@symbo.ls/windows
v3.14.709
Published
Unified modal / dialog system. One `ModalRoot` at the app shell hosts a stack of layers; each layer is a separate visible window so a modal can open another modal. Replaces:
Maintainers
Keywords
Readme
@symbo.ls/windows
Unified modal / dialog system. One ModalRoot at the app shell hosts a
stack of layers; each layer is a separate visible window so a modal can
open another modal. Replaces:
- per-component
Modaldefinitions that each declared their ownstateKeyon root state - native
window.alert/window.confirm/window.prompt(which block the JS event loop and ship the host OS's chrome instead of the app's design system)
Wiring
Once per shell — at the app's entry point alongside the SDK + router plugins:
import { windowsComponents } from '@symbo.ls/windows/components'
import { windowsFunctions } from '@symbo.ls/windows/functions'
// 1. Register the plugin's components + functions into context
Object.assign(context.components, windowsComponents)
Object.assign(context.functions, windowsFunctions)
// 2. Add ModalRoot once at the root of your shell tree
const shell = {
Header: {},
Main: {},
Footer: {},
ModalRoot: {}, // ← this line, that's the whole wiring
}Reuses the existing ModalWindow, ModalHeader, ModalFooter,
Button, Input, Hgroup, Icon components from the design system —
no new chrome added.
Calling
From any element handler:
el.call('confirmDialog', {
title: 'Delete event?',
message: 'This permanently removes "Standup" from everyone’s calendar.',
confirmText: 'Delete',
confirmTheme: 'red',
icon: 'trash',
onConfirm: () => el.call('deleteEvent', id),
onCancel: () => {}, // optional
})
el.call('alertDialog', {
title: 'Saved',
message: 'Your changes are live.',
onOk: () => {}, // optional
})
el.call('promptDialog', {
title: 'Rename page',
message: 'New name:',
defaultValue: oldName,
placeholder: 'Untitled',
onConfirm: (value) => el.call('renamePage', value),
onCancel: () => {}, // optional
})
// Custom content — caller provides a DOMQL fragment
const layerId = el.call('openModal', {
template: 'custom',
content: {
H2: { text: 'Custom modal' },
P: { text: 'Anything DOMQL goes here.' },
Button: { text: 'Done', onClick: (e, btn) => btn.call('closeModal', layerId) },
},
})Stacking
Up to 5 layers (ModalLayer_0 through ModalLayer_4). Layer N
z-indexes at 2000 + N*10, so layer 1 always renders above layer 0.
ESC closes the topmost layer. Backdrop clicks dismiss unless
dismissOnBackdrop: false was passed to openModal.
Rules followed
- FA101 / FA102 — flat
el.Xprops +onClick/onInit/onDestroy(noel.props.X, noon.click). - FA201 — mutable state lives in
state.stackon ModalRoot, never on globalScope. - FA204 — Prompt's input draft uses
scope: { value: '' }. - FA207 — top-level
const _layerText = (el, field, fallback) => ...helpers, no nestedfunctiondeclarations inside handlers. - FA514 — ESC listener attached via
onInit/onDestroylifecycle, not at module load. The previouswindow.__modalEscBoundpattern inworkspace/packages/shared/components/Modal.jsis the exact anti-pattern this plugin retires. - REUSE rule — Window chrome extends the shared
ModalWindow; buttons extendButton; inputs extendInput; icons extendIcon. No re-definition of built-ins.
