@zeyos/zx 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +197 -0
- package/DESIGN-SYSTEM.md +136 -0
- package/MIGRATION.md +155 -4
- package/README.md +51 -14
- package/dist/types/compat/map/data.d.ts +3 -2
- package/dist/types/components/account-menu/account-menu.d.ts +144 -0
- package/dist/types/components/app-rail/app-rail.d.ts +250 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
- package/dist/types/components/avatar/avatar.d.ts +100 -0
- package/dist/types/components/chart/chart.d.ts +321 -0
- package/dist/types/components/date-picker/month-picker.d.ts +6 -0
- package/dist/types/components/dialog/dialog.d.ts +9 -0
- package/dist/types/components/dock/dock.d.ts +495 -0
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +14 -0
- package/dist/types/components/field-upload/field-upload.d.ts +37 -4
- package/dist/types/components/form/form.d.ts +25 -3
- package/dist/types/components/grid/grid.d.ts +172 -0
- package/dist/types/components/launcher/launcher.d.ts +300 -0
- package/dist/types/components/layout/layout.d.ts +4 -4
- package/dist/types/components/menu-button/menu-button.d.ts +10 -2
- package/dist/types/components/modal/modal.d.ts +25 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
- package/dist/types/components/questionnaire/items.d.ts +327 -0
- package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
- package/dist/types/components/select/select.d.ts +36 -1
- package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
- package/dist/types/components/sheet/sheet.d.ts +294 -0
- package/dist/types/components/split-view/split-view.d.ts +2 -21
- package/dist/types/components/tabbox/tabbox.d.ts +12 -3
- package/dist/types/components/table/table.d.ts +219 -12
- package/dist/types/components/tooltip/tooltip.d.ts +2 -2
- package/dist/types/components/value-list/value-list.d.ts +24 -3
- package/dist/types/core/dom.d.ts +114 -0
- package/dist/types/core/drag-axis.d.ts +111 -0
- package/dist/types/core/position.d.ts +4 -2
- package/dist/types/index.d.ts +12 -2
- package/dist/zx-compat.esm.js +943 -311
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +7893 -2252
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +826 -114
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +1720 -243
- package/dist/zx.esm.js +8227 -2599
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +8227 -2599
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +2597 -255
- package/docs/llms.md +475 -37
- package/docs/llms.txt +21 -9
- package/package.json +4 -2
- package/src/compat/globals.js +3 -1
- package/src/compat/map/data.js +31 -9
- package/src/components/account-menu/account-menu.css +70 -0
- package/src/components/account-menu/account-menu.js +208 -0
- package/src/components/app-rail/app-rail.css +107 -0
- package/src/components/app-rail/app-rail.js +668 -0
- package/src/components/app-sidebar/app-sidebar.css +68 -0
- package/src/components/app-sidebar/app-sidebar.js +388 -0
- package/src/components/avatar/avatar.css +61 -0
- package/src/components/avatar/avatar.js +222 -0
- package/src/components/button/button.css +0 -1
- package/src/components/chart/chart.css +61 -0
- package/src/components/chart/chart.js +518 -0
- package/src/components/date-picker/date-range-picker.css +7 -0
- package/src/components/date-picker/month-picker.js +9 -0
- package/src/components/dialog/dialog.js +11 -4
- package/src/components/dock/dock.css +228 -0
- package/src/components/dock/dock.js +977 -0
- package/src/components/dropdown/dropdown.js +1 -1
- package/src/components/field/field.js +2 -0
- package/src/components/field-upload/field-upload.js +9 -4
- package/src/components/fieldset/fieldset.css +7 -0
- package/src/components/form/form.css +7 -0
- package/src/components/form/form.js +6 -3
- package/src/components/grid/grid.css +6 -0
- package/src/components/grid/grid.js +213 -0
- package/src/components/launcher/launcher.css +100 -0
- package/src/components/launcher/launcher.js +557 -0
- package/src/components/layout/layout.js +2 -2
- package/src/components/menu-button/menu-button.js +27 -2
- package/src/components/modal/modal.css +5 -1
- package/src/components/modal/modal.js +37 -2
- package/src/components/multi-value-editor/multi-value-editor.css +99 -15
- package/src/components/multi-value-editor/multi-value-editor.js +237 -19
- package/src/components/navigation-bar/navigation-bar.css +7 -0
- package/src/components/questionnaire/items.js +370 -0
- package/src/components/questionnaire/questionnaire.css +387 -0
- package/src/components/questionnaire/questionnaire.js +1033 -0
- package/src/components/search/search.css +10 -2
- package/src/components/select/select.css +6 -1
- package/src/components/select/select.js +99 -11
- package/src/components/sheet/sheet-stack.js +304 -0
- package/src/components/sheet/sheet.css +351 -0
- package/src/components/sheet/sheet.js +551 -0
- package/src/components/split-view/split-view.js +76 -161
- package/src/components/stepper/stepper.css +12 -2
- package/src/components/tabbox/tabbox.css +13 -3
- package/src/components/tabbox/tabbox.js +34 -10
- package/src/components/table/table.css +43 -0
- package/src/components/table/table.js +523 -45
- package/src/components/tooltip/tooltip.js +5 -2
- package/src/components/value-list/value-list.js +6 -3
- package/src/core/dom.js +302 -0
- package/src/core/drag-axis.js +203 -0
- package/src/core/position.js +68 -22
- package/src/index.js +19 -2
- package/styles/tokens/dark.css +15 -13
- package/styles/tokens/global.css +17 -5
- package/styles/tokens/semantic.css +14 -8
- package/styles/tokens/themes.css +72 -0
- package/styles/zx.css +11 -1
- package/dist/types/components/permission/permission.d.ts +0 -93
- package/src/components/permission/permission.css +0 -47
- package/src/components/permission/permission.js +0 -198
package/docs/llms.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Zx — agent reference
|
|
1
|
+
# Xenon Design System (Zx) — agent reference
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> Xenon is the dependency-free design system for ZeyOS business applications; Zx is its
|
|
4
|
+
> vanilla-JavaScript implementation. ES2022 modules, WAI-ARIA-accessible native controls, semantic `--zx-*` design
|
|
5
5
|
> tokens (light/dark + cozy/compact), and lifecycle-safe components. Zero runtime dependencies.
|
|
6
6
|
> This file is the single, complete reference a coding agent needs to build UI with Zx; each
|
|
7
7
|
> component below is wrapped in `<!-- doc:<name> -->` markers so tools can extract a single
|
|
@@ -53,8 +53,27 @@ Load `zx.css`, then set theme/density on any ancestor (usually `<html>`):
|
|
|
53
53
|
<html data-zx-theme="dark" data-zx-density="compact">
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Themes: `light` | `dark` | `auto` (follows OS). Density: `cozy` | `compact`.
|
|
57
|
-
|
|
56
|
+
Themes: `light` | `dark` | `auto` (follows OS). Density: `cozy` | `compact`.
|
|
57
|
+
|
|
58
|
+
**Accent presets.** A second, orthogonal attribute picks the accent ramp:
|
|
59
|
+
`data-zx-preset="zx" | "zeyos" | "ocean" | "violet" | "rose" | "slate"` (`zx` is the default and
|
|
60
|
+
need not be set). A preset repoints five tier-1 tokens — `--zx-accent-300` … `--zx-accent-700` —
|
|
61
|
+
and the semantic tier takes its own stop per polarity (600/700 on light surfaces, 300/400/500 on
|
|
62
|
+
dark), so one five-line block rethemes light, dark, and auto together. Status colours are not part
|
|
63
|
+
of a preset. Define your own the same way, or build one at `/theme.html` (the theme studio, which
|
|
64
|
+
renders every component family live and exports the CSS):
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
:root {
|
|
68
|
+
--zx-accent-300: #c4b4ff; --zx-accent-400: #a684ff; --zx-accent-500: #8e51ff;
|
|
69
|
+
--zx-accent-600: #7f22fe; --zx-accent-700: #7008e7;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Keep the 600 stop at 4.5:1 or better against white and the 400 stop at 4.5:1 against the dark page
|
|
74
|
+
— that is what makes `--zx-color-on-accent` legible without being restated per theme.
|
|
75
|
+
|
|
76
|
+
Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
|
|
58
77
|
`--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-border(-strong/-control)`,
|
|
59
78
|
`--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
|
|
60
79
|
`--zx-color-danger/warning/success/info(+ -bg)`, `--zx-focus-ring`, `--zx-control-height/-radius`,
|
|
@@ -192,8 +211,9 @@ reflection and ElementInternals form association: `<zx-toggle>`, `<zx-check-butt
|
|
|
192
211
|
|
|
193
212
|
Load `zx-compat.global.js` (or import `zx-compat.esm.js`) →
|
|
194
213
|
`window.gx.{core,ui,util,zeyos,bootstrap}` wrapping Zx components (constructor/option/event-name
|
|
195
|
-
translation, MooTools-style `addEvent`). `gx.compat.installGlobals()` (opt-in) installs
|
|
196
|
-
`_()`, `String.htmlSpecialChars`, and the element
|
|
214
|
+
translation, MooTools-style `addEvent`). `gx.compat.installGlobals()` (opt-in) installs its gx
|
|
215
|
+
`__()` parser only when that name is free, plus `_()`, `String.htmlSpecialChars`, and the element
|
|
216
|
+
`store/retrieve` shim. Full namespace listing in
|
|
197
217
|
the "gx compatibility layer" component section below; class map and unsupported legacy APIs in
|
|
198
218
|
`MIGRATION.md`.
|
|
199
219
|
|
|
@@ -399,21 +419,92 @@ Search input with embedded search and clear buttons (`role="search"`).
|
|
|
399
419
|
- **Events** — `input {value}` (debounced), `submit {value}` (Enter or the button), `clear`.
|
|
400
420
|
<!-- /doc -->
|
|
401
421
|
|
|
422
|
+
<!-- doc:launcher -->
|
|
423
|
+
### Launcher — application and record search
|
|
424
|
+
|
|
425
|
+
A native-dialog launcher for applications, recent records, commands, and grouped remote results.
|
|
426
|
+
Zx owns ranking, focus, request cancellation, and presentation; ZeyOS owns the catalogue,
|
|
427
|
+
permissions, recent-history/cache policy, and the route or command ultimately invoked.
|
|
428
|
+
|
|
429
|
+
- **Constructor** — `new Launcher(null, options)` owns a body-level dialog;
|
|
430
|
+
`new Launcher(existingDialog, options)` enhances and later restores an existing `<dialog>`.
|
|
431
|
+
- **Options** — `items`, abortable `sources: [{id, label?, minQuery?, load(query, {signal})}]`,
|
|
432
|
+
`query`, `debounce: 250`, `minQuery: 0`, `maxResults: 100`, `placeholder`, `label`, `emptyText`,
|
|
433
|
+
`loadingText`, and `shortcut: 'mod+k'|false`.
|
|
434
|
+
- **Items** — `{id, label, description?, keywords?, group?, icon?, badge?, value?, href?, target?,
|
|
435
|
+
invoke?, pinned?, disabled?}`. Links stay native so modified and middle clicks work; non-link
|
|
436
|
+
commands may provide an application-owned `invoke` callback.
|
|
437
|
+
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `focus()`, `setQuery()`, `getQuery()`,
|
|
438
|
+
`setItems()`, `setSources()`, `destroy()`.
|
|
439
|
+
- **Events** — cancelable `select {item, value, source, query}`, `query {query}`, `open`, `close`,
|
|
440
|
+
and `error {source, error}`. Preventing `select` suppresses the supplied link or callback.
|
|
441
|
+
- **Ranking** — local results are case/diacritic-insensitive and deterministic: exact label,
|
|
442
|
+
label prefix, word prefix, label substring, identifier substring, then acronym prefix. Pinned
|
|
443
|
+
items retain an explicit order ahead of otherwise equal results. A new async query aborts and
|
|
444
|
+
ignores stale source work.
|
|
445
|
+
- **Keyboard** — Cmd+K on macOS or Ctrl+K elsewhere opens by default; the shortcut is ignored in
|
|
446
|
+
editable controls, composition, and modal contexts. Arrow keys, Home/End, and Enter operate the
|
|
447
|
+
single listbox selection model; Escape closes and restores focus.
|
|
448
|
+
<!-- /doc -->
|
|
449
|
+
|
|
450
|
+
<!-- doc:avatar -->
|
|
451
|
+
### Avatar
|
|
452
|
+
|
|
453
|
+
A fixed-geometry user image with deterministic initials fallback. Image loading or failure never
|
|
454
|
+
changes layout, and the component is decorative by default so adjacent identity text is not
|
|
455
|
+
announced twice.
|
|
456
|
+
|
|
457
|
+
- **Options** — `src`, `name`, `initials`, `label`, `size: 'sm'|'md'|'lg'|<pixels>`,
|
|
458
|
+
`shape: 'circle'|'rounded'|'square'`, `status: 'online'|'away'|'busy'|'offline'|null`, and
|
|
459
|
+
`statusLabel`.
|
|
460
|
+
- **Methods** — `set(values)`, `setStatus(status, label?)`, `hasImage()`, `destroy()`.
|
|
461
|
+
- **Accessibility** — set `label` only when the avatar stands alone. Presence is supplementary
|
|
462
|
+
status, not the account's accessible name; supply `statusLabel` when it conveys information.
|
|
463
|
+
<!-- /doc -->
|
|
464
|
+
|
|
465
|
+
<!-- doc:account-menu -->
|
|
466
|
+
### AccountMenu
|
|
467
|
+
|
|
468
|
+
Composes `Avatar` and `MenuButton` into one account trigger and APG menu. The popup repeats the
|
|
469
|
+
identity before its actions; Zx never decides role visibility, changes preferences, signs a user
|
|
470
|
+
out, or mutates the session.
|
|
471
|
+
|
|
472
|
+
- **Options** — `account: {name, secondary, src?, initials?, status?, statusLabel?}`, `items`,
|
|
473
|
+
`compact: false`, `label`, and any top/bottom/left/right `placement`.
|
|
474
|
+
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setAccount()`, `setItems()`, `focus()`,
|
|
475
|
+
`getPanel()`, `destroy()`.
|
|
476
|
+
- **Events** — cancelable `select {value, item}`, `open`, and `close`. A canceled selection does
|
|
477
|
+
not run the item's callback and leaves the menu open, so an application can reject or confirm an
|
|
478
|
+
action without rebuilding the account surface.
|
|
479
|
+
- **Accessibility** — compact mode is visually avatar-only but keeps the full account-menu name on
|
|
480
|
+
its single native trigger; MenuButton retains focus return, typeahead, and arrow-key behavior.
|
|
481
|
+
<!-- /doc -->
|
|
482
|
+
|
|
402
483
|
<!-- doc:select -->
|
|
403
484
|
### Select — APG editable combobox
|
|
404
485
|
|
|
405
486
|
Unifies single-select, local filtering, and async loading.
|
|
406
487
|
|
|
407
|
-
- **Options** — `items: []`, `valueKey: 'ID'` (string key or `(item)=>id`),
|
|
408
|
-
(string or `(item)=>string`), `renderItem`, `renderValue`, `value`,
|
|
409
|
-
`clearable: false`, `filter: false | 'local' | async (query)=>items`,
|
|
410
|
-
`minQuery: 0`, `debounce: 200`, `listHeight: 280`, `groupKey`.
|
|
488
|
+
- **Options** — `items: []`, `fixedItems: []`, `valueKey: 'ID'` (string key or `(item)=>id`),
|
|
489
|
+
`labelKey: 'name'` (string or `(item)=>string`), `renderItem`, `renderValue`, `value`,
|
|
490
|
+
`disabled`, `placeholder`, `clearable: false`, `filter: false | 'local' | async (query)=>items`,
|
|
491
|
+
`searchKeys`, `minQuery: 0`, `debounce: 200`, `listHeight: 280`, `groupKey`.
|
|
411
492
|
- **Getters** — `.value`, `.selected`.
|
|
412
|
-
- **Methods** — `set(id, {silent})`, `setItems()`, `reset()`, `open()`/`close()`,
|
|
493
|
+
- **Methods** — `set(id, {silent})`, `setItems()`, `setFixedItems()`, `reset()`, `open()`/`close()`,
|
|
413
494
|
`enable()`/`disable()`, `focus()`.
|
|
414
495
|
- **Events** — `change {value, item}` (`item` is null on clear), `open`, `close`, `query {query}`,
|
|
415
496
|
`loaded {items}`.
|
|
416
|
-
- **
|
|
497
|
+
- **Fixed choices** — `fixedItems` pins entries to the top of the list, separated by a rule (the
|
|
498
|
+
rule is dropped when `groupKey` is on, since a heading already divides them). They belong to the
|
|
499
|
+
control, not to its data: a `filter` function replaces `items` on every query, while the pinned
|
|
500
|
+
entries are narrowed locally against the same query and stay resolvable by `set()`. They survive
|
|
501
|
+
`setItems()` and are shown below `minQuery`. Use them for choices with no row in the source —
|
|
502
|
+
"Unassigned", "Everyone", "Private".
|
|
503
|
+
- **Presets** — `Select.priority(target, opts)`, a 5-level priority picker;
|
|
504
|
+
`Select.permission(target, {groups, value})`, the record-access picker with Private and Public
|
|
505
|
+
pinned above the groups. Its value is the tri-state ZeyOS stores (`false` private, `true` public,
|
|
506
|
+
or a group ID), `groups` takes an array or a loader `(query)=>items`, and the labels come from
|
|
507
|
+
`msg` (`permission.private`, `permission.public`).
|
|
417
508
|
- **Keyboard** (APG combobox) — ArrowDown/Alt+ArrowDown open; arrows navigate and wrap; Home/End;
|
|
418
509
|
Enter selects; Esc closes; Tab closes; printable characters filter (editable) or run a typeahead
|
|
419
510
|
(readonly). `aria-activedescendant` tracks the active option.
|
|
@@ -646,6 +737,102 @@ Structured modal: a header with title and close button, a body, and footer butto
|
|
|
646
737
|
`Dialog.prompt({ title, message, value })` → string|null.
|
|
647
738
|
<!-- /doc -->
|
|
648
739
|
|
|
740
|
+
<!-- doc:sheet -->
|
|
741
|
+
### Sheet
|
|
742
|
+
|
|
743
|
+
Edge-anchored surface — a `Dialog` attached to one side of the viewport instead of floating in the
|
|
744
|
+
middle. One primitive covers both side sheets and mobile drawers, which differ mainly in the edge they take, so
|
|
745
|
+
`side` replaces what would otherwise be two components. Inherits the whole Dialog anatomy: title,
|
|
746
|
+
footer buttons, switchable views, and focus restoration.
|
|
747
|
+
|
|
748
|
+
- **Options** — `side: 'end'` (`'start' | 'end' | 'top' | 'bottom'`, logical), `modal: true`
|
|
749
|
+
(`true | 'trap-focus' | false`), `backdrop: 'dim'` (`'dim' | 'blur' | 'none'`), `size: null`
|
|
750
|
+
(CSS length or px number, applied to whichever axis `side` implies), `closeButton: true`,
|
|
751
|
+
plus everything Dialog takes — `title`, `content`, `buttons`, `closable`, `lightDismiss`
|
|
752
|
+
(defaults to `true` here, unlike Modal), `destroyOnClose`.
|
|
753
|
+
- **Methods** — `open()`, `close(result?)`, `setSide(side)`, `getSide()`, `setSize(size)`,
|
|
754
|
+
`isModal()`, `isDocked()`, `isOpen()`, `getSize()`, `snapTo(target)`, plus Dialog's `setTitle()`, `setContent()`,
|
|
755
|
+
`setButtons()`, `addView()`, `showView()`.
|
|
756
|
+
- **Events** — `open`, `close {result}`, `cancel` (preventable with `event.preventDefault()`),
|
|
757
|
+
`resize {size, ratio}`, `dockchange {docked, dock}`.
|
|
758
|
+
- **Docked** — a sheet has two lives, and it does not choose between them: `Dock.adopt(sheet)`
|
|
759
|
+
makes it a track in that dock's flow instead of an overlay, and the dock then owns its side,
|
|
760
|
+
size, and resizing. A docked sheet is always non-modal, `close()` collapses its track to zero
|
|
761
|
+
rather than sliding away, and Escape and outside-click stop dismissing it — it is part of the
|
|
762
|
+
layout, not something covering it. `Dock.release(sheet)` hands it back. Nothing is rebuilt
|
|
763
|
+
across a handoff: the element moves and reopens in place, so DOM state and listeners survive,
|
|
764
|
+
and no `open`/`close` is emitted for what is only a change of address.
|
|
765
|
+
- **Modality** — `true` delegates focus containment, page inertness, Escape, and the backdrop to
|
|
766
|
+
the browser through `showModal()`. `'trap-focus'` and `false` open with `show()`, so the page
|
|
767
|
+
keeps scrolling and stays interactive; Escape and outside-click are re-implemented, and
|
|
768
|
+
`'trap-focus'` adds `focusTrap()` on top. Only a modal sheet renders a `::backdrop`, so
|
|
769
|
+
`backdrop` is meaningful only when `modal` is `true`.
|
|
770
|
+
- **Resizing and detents** — `resizable` makes the inner edge draggable between `min` and `max`.
|
|
771
|
+
`snap` gives it detents: a number at or below 1 is a fraction of the viewport along the sheet's
|
|
772
|
+
axis, anything larger is pixels, and a string is any CSS length (`'320px'`, `'90%'`). With
|
|
773
|
+
detents a drag settles on the nearest one rather than where it was released, and `snapTo(index)`
|
|
774
|
+
or `snapTo(value)` moves there directly. Dragging below half the smallest allowed size dismisses
|
|
775
|
+
a `closable` sheet, so resize, detent, and swipe-to-dismiss are one gesture whose meaning is
|
|
776
|
+
decided when the pointer settles. `getSize()` reports the current pixel size. A sheet that is
|
|
777
|
+
neither `resizable` nor snapping still has a draggable handle where `handle` shows one — that is
|
|
778
|
+
what makes a drawer dismissible — but the drag may only take size away, never add it, so it
|
|
779
|
+
cannot be grown by dragging when nothing asked for resizing.
|
|
780
|
+
- **Handle** — `handle: 'auto'` shows a grab handle on `top` and `bottom` sheets, which is what
|
|
781
|
+
makes them read as drawers; `true` and `false` force it either way. It exists whenever the sheet
|
|
782
|
+
is `resizable` or has `snap`, visible or not. It is a `separator` with live
|
|
783
|
+
`aria-valuenow`/`aria-valuemin`/`aria-valuemax`: arrow keys along the axis step it by 16px, Shift
|
|
784
|
+
by 64px. A docked sheet hides its handle — the dock's own divider resizes it there.
|
|
785
|
+
- **Custom properties** — `--zx-sheet-size` (written by `size`; the per-side default otherwise)
|
|
786
|
+
and `--zx-sheet-radius` (inner corners of a top/bottom sheet, default `--zx-radius-xl`) are
|
|
787
|
+
internal geometry, not published styling hooks — prefer the options. `--zx-overlay-blur` is a
|
|
788
|
+
tier-2 semantic token and is public: it sets the `'blur'` backdrop's radius, default `8px`.
|
|
789
|
+
- **Motion** — entry and exit both animate, via `@starting-style` and
|
|
790
|
+
`transition-behavior: allow-discrete` on `display`/`overlay`; without them the sheet appears and
|
|
791
|
+
disappears instantly. All of it sits inside `prefers-reduced-motion: no-preference`.
|
|
792
|
+
- **Keyboard and screen readers** — identical to Dialog: the native `<dialog>` supplies the role
|
|
793
|
+
and `aria-labelledby` points at the title. Escape closes when `closable`, focus moves to the
|
|
794
|
+
`[autofocus]` control or the first focusable descendant on open, and returns to the opener on
|
|
795
|
+
close. Tab cycles within the sheet when `modal` is `true` or `'trap-focus'`.
|
|
796
|
+
<!-- /doc -->
|
|
797
|
+
|
|
798
|
+
<!-- doc:sheet-stack -->
|
|
799
|
+
### SheetStack
|
|
800
|
+
|
|
801
|
+
An ordered group of Sheets that reads as one drill-down. Deliberately tiny, because the browser
|
|
802
|
+
already does the hard part: nested dialogs stack in the top layer by open order, so there is no
|
|
803
|
+
z-index bookkeeping, and Escape closes only the topmost, so unwinding one level at a time is
|
|
804
|
+
native. What is left is which sheet sits at which depth, written onto the elements as `data-depth`
|
|
805
|
+
and `--zx-sheet-depth` for CSS to interpret.
|
|
806
|
+
|
|
807
|
+
Owns no element, so it is a controller rather than a `Component` — constructed with options
|
|
808
|
+
alone, `new SheetStack({…})`.
|
|
809
|
+
|
|
810
|
+
- **Options** — `layout: 'stack'` (`'stack' | 'cascade'`), `offset: 24`, `scale: 0.04`, `max: 3`.
|
|
811
|
+
- **Methods** — `push(sheet)`, `pop()`, `popTo(sheet)`, `clear()`, `top()`, `size()`, `sheets()`,
|
|
812
|
+
`has(sheet)`, `on/off/once`, `destroy()`.
|
|
813
|
+
- **Events** — `push {sheet, depth}`, `pop {sheet}`. `pop` fires however a sheet left — popped,
|
|
814
|
+
dismissed with Escape, or closed by its own button.
|
|
815
|
+
- **Layouts** — `stack` slides each covered sheet back toward its own edge, scales it down, and
|
|
816
|
+
takes it out of the tab order; only the top one is usable, which is the drill-down feel.
|
|
817
|
+
`cascade` shifts each covered sheet clear of the ones in front — by their measured size, not a
|
|
818
|
+
guess — so they sit side by side and all stay usable. `cascade` is usually the right one for an
|
|
819
|
+
ERP screen, where the parent record should stay readable while a line item is edited.
|
|
820
|
+
- **Depth** — numbered from the top: the visible sheet is `0`. Past `max` a sheet stops being
|
|
821
|
+
drawn but stays open and in the stack. In a `stack` only the topmost backdrop shades the page;
|
|
822
|
+
one per depth would compound into black. A `cascade` shades nothing — a backdrop paints directly
|
|
823
|
+
beneath its own dialog in the top layer, so the top sheet's would dim exactly the sheets the
|
|
824
|
+
layout exists to keep readable. Pair a cascade with `modal: false` when the page behind it
|
|
825
|
+
should stay live as well.
|
|
826
|
+
- **Ownership** — the stack never owns its sheets. `destroy()` empties it and strips the depth
|
|
827
|
+
styling without closing anything.
|
|
828
|
+
- **Accessibility** — for `stack`, covered sheets are `inert`. The browser does that on its own
|
|
829
|
+
for stacked *modal* dialogs, but a non-modal stack gets nothing, so the attribute is set either
|
|
830
|
+
way. `cascade` deliberately leaves them interactive.
|
|
831
|
+
- **Gotcha** — `close()` on a dialog dispatches its `close` event in a queued task, not
|
|
832
|
+
synchronously. `pop()` therefore removes the sheet from the stack itself rather than waiting for
|
|
833
|
+
the event, so `popTo()` and `clear()` can loop without racing it.
|
|
834
|
+
<!-- /doc -->
|
|
835
|
+
|
|
649
836
|
<!-- doc:dropdown -->
|
|
650
837
|
### Dropdown
|
|
651
838
|
|
|
@@ -653,7 +840,8 @@ Generic anchored popover in the top layer, constructed as `new Dropdown(anchor,
|
|
|
653
840
|
light-dismisses on an outside click or Esc, and mirrors `aria-expanded` onto the anchor.
|
|
654
841
|
|
|
655
842
|
- **Options** — `content`,
|
|
656
|
-
`placement: 'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'
|
|
843
|
+
`placement: 'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'` plus the matching
|
|
844
|
+
`left*` and `right*` placements, `offset: 4`,
|
|
657
845
|
`matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false`.
|
|
658
846
|
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setContent()`, `getPanel()`.
|
|
659
847
|
- **Events** — `open`, `close`.
|
|
@@ -666,8 +854,11 @@ A button that opens a `role="menu"`.
|
|
|
666
854
|
|
|
667
855
|
- **Options** — `label`, `icon`, `kind`,
|
|
668
856
|
`items: [{ label, icon?, value?, disabled?, danger?, onselect? } | '-']`, `placement`.
|
|
669
|
-
- **Methods** — `setItems()`, `open()`, `close()`, `setLabel()
|
|
857
|
+
- **Methods** — `setItems()`, `open()`, `close()`, `setLabel()`, `getTrigger()`, `getPanel()`,
|
|
858
|
+
`focusFirst()`, `focusLast()`.
|
|
670
859
|
- **Events** — `select {value, item}`, `open`, `close`.
|
|
860
|
+
- **Cancellation** — `select` is cancelable. Preventing it skips the item's `onselect` callback and
|
|
861
|
+
leaves the menu open, which is the composition seam used by `AccountMenu` and rail flyouts.
|
|
671
862
|
- **Keyboard** — ArrowDown/Enter/Space open and focus the first item; ArrowUp opens and focuses the
|
|
672
863
|
last; arrows cycle; Home/End; typeahead; Enter/Space select; Esc/Tab close.
|
|
673
864
|
<!-- /doc -->
|
|
@@ -705,7 +896,11 @@ parked at the pointer, so it flips near a viewport edge like every other floatin
|
|
|
705
896
|
|
|
706
897
|
A description bubble anchored to another element. The panel is a manual popover positioned by the kernel's `position()`, so it escapes clipping and stacking contexts and flips near a viewport edge; it carries `role="tooltip"` and a generated id, and the anchor gets `aria-describedby` pointing at it only while it is open — closing puts the anchor's original value back, including no value at all. The bubble is never interactive: `pointer-events: none` keeps it from stealing the hover it depends on, and nothing inside it can take focus. Hover opens it after `delay`; keyboard focus opens it immediately, because a keyboard user cannot hover a moment longer; a touch pointer never opens it, since a bubble with no "move away" gesture would simply stick. Only one is ever visible at a time, which falls out of the dismissal rules rather than a registry.
|
|
707
898
|
|
|
708
|
-
- **Options** — `content` (string, Node, or a function re-evaluated on every open), `placement`
|
|
899
|
+
- **Options** — `content` (string, Node, or a function re-evaluated on every open), `placement`
|
|
900
|
+
(`'top'` default, plus top/bottom/left/right and their start/end variants), `offset` (6), `delay`
|
|
901
|
+
(400), `hideDelay` (80), `maxWidth` (260; a number is pixels, a string is any CSS length),
|
|
902
|
+
`trigger` (`'both'` default, or `'hover'`, `'focus'`, `'manual'` — `manual` installs no listeners
|
|
903
|
+
at all and leaves everything to the methods), `disabled` (false).
|
|
709
904
|
- **Methods** — `show()`, `hide()`, `toggle()`, `isOpen()`, `setContent(content)`, `enable()`, `disable()`, `isDisabled()`, `getAnchor()`, `destroy()`. Convenience factories: `tooltip(anchor, contentOrOptions)` takes either a content value or a full option object, and `describe(anchor, text)` is the one-liner replacement for a `title` attribute. Both return the instance.
|
|
710
905
|
- **Events** — `open`, `close`. Both fire only on a real transition, so a `show()` on an open tooltip is silent.
|
|
711
906
|
- **Keyboard** — Focusing the anchor opens the tooltip at once; moving focus away closes it. `Escape` closes it without moving focus, satisfying WCAG 1.4.13. A `pointerdown` anywhere closes it, and focus that arrives with a click does not re-open it.
|
|
@@ -730,22 +925,32 @@ Sortable, selectable, sticky-header data table — one component covering both t
|
|
|
730
925
|
horizontally, and multi-select adds a tri-state header checkbox plus Shift+click range select.
|
|
731
926
|
|
|
732
927
|
- **Options** — `columns: [{ id, label, sortable?, width ('120px'|'2fr'|'auto'), align,
|
|
928
|
+
type?: 'text'|'number'|'currency'|'percent'|'unit', locale?, decimals?, currency?, unit?,
|
|
733
929
|
render?: (row,i)=>Node|string, sortValue?, headerTitle? }]`, `data`, `rowId: 'ID'`,
|
|
734
930
|
`sort: { id, dir }`, `sortMode: 'local'|'server'`, `selectable: false|'single'|'multi'`,
|
|
735
|
-
`stickyHeader: true`, `height`, `emptyText`, `rowClass`, `zebra: true
|
|
931
|
+
`stickyHeader: true`, `height`, `emptyText`, `rowClass`, `zebra: true`, and
|
|
932
|
+
`hierarchy: false|{parentId, column?, expanded?}`. Locale, currency, unit, and decimals may be
|
|
933
|
+
row callbacks; values remain numbers in row data while display formatting is locale-aware.
|
|
736
934
|
- **Methods** — `setData()`, `addData()`, `updateRow(id,row)`, `removeRow(id)`, `getRow(id)`,
|
|
737
935
|
`getData()`, `empty()`, `setSort(id,dir)`, `getSelection()`, `setSelection(ids)`,
|
|
738
936
|
`clearSelection()`, `setLoading(bool)` (busy/skeleton state — dims rows, shows an indeterminate
|
|
739
937
|
top bar, sets `aria-busy`, and is cleared by the next `setData`).
|
|
740
938
|
- **Events** — `rowclick {row, id, index, event}`, `rowdblclick`, `sort {id, dir}`,
|
|
741
|
-
`selectionchange {rows, ids}`, `datachange {rows}`.
|
|
939
|
+
`selectionchange {rows, ids}`, `datachange {rows}`, and `rowtoggle {row, id, expanded}`.
|
|
940
|
+
- **Hierarchy** — flat parent references are projected as an ARIA `treegrid`; `getData()` and all
|
|
941
|
+
editing/mutation APIs stay flat and ID-based. Orphans become roots, cycles render once, local
|
|
942
|
+
sort reorders siblings without mixing levels, and growing counts visible rows. Use
|
|
943
|
+
`toggleRow()`, `expandRow()`, `collapseRow()`, `expandAll()`, `collapseAll()`, and
|
|
944
|
+
`getExpanded()`. The native disclosure button works by pointer and keyboard without selecting
|
|
945
|
+
or clicking the row.
|
|
742
946
|
- **Editing** — `editMode: false|'cell'|'row'` (default `false`, and completely inert until set).
|
|
743
947
|
`'cell'` edits one cell, `'row'` opens every editable cell of the row and commits them as a unit.
|
|
744
948
|
Editors are sized into the cell without changing row height, and an editing cell carries
|
|
745
949
|
`data-editing="true"` (`data-invalid="true"` plus an `aria-live` message when a validator rejects).
|
|
746
950
|
- **Editable columns** — extra column keys: `editable: false|true|'text'|'number'|'select'|'date'|
|
|
747
|
-
'checkbox'|'textarea'|((row)=>boolean)` (`true`
|
|
748
|
-
|
|
951
|
+
'checkbox'|'textarea'|((row)=>boolean)` (`true` infers `'number'` for number/currency/percent/unit
|
|
952
|
+
columns and `'text'` otherwise; a function is evaluated per row, so individual rows stay
|
|
953
|
+
read-only), `options` for `'select'` (`[{value,label}]`, a `{value: label}` map
|
|
749
954
|
whose keys are always strings, or `(row)=>items`), `editorProps` (forwarded to the underlying
|
|
750
955
|
NumberField/Select/Datebox/Toggle or to the generated `<input>`/`<textarea>`),
|
|
751
956
|
`editor: (row, api)=>Node` for a fully custom editor (`api` is `{value, commit(value), cancel(),
|
|
@@ -754,6 +959,10 @@ horizontally, and multi-select adds a tri-state header checkbox plus Shift+click
|
|
|
754
959
|
seeds the text of a `'text'`/`'textarea'` editor, `parse` converts every editor's raw output back),
|
|
755
960
|
and `validate: (value,row)=>true|string` (a returned string marks the cell invalid, shows the
|
|
756
961
|
message, and refuses the commit).
|
|
962
|
+
- **Transaction editors** — currency and unit columns reuse `NumberField` and show the resolved
|
|
963
|
+
currency/unit as its suffix unless `editorProps.unit` overrides it. This is a generic typed-grid
|
|
964
|
+
contract: application code remains responsible for tax, discount, subtotal, and accounting
|
|
965
|
+
formula rules.
|
|
757
966
|
- **Editing methods** — `startEdit(rowId, columnId)`, `commitEdit()`, `cancelEdit()`, `isEditing()`,
|
|
758
967
|
`getEditing()` → `{id, columnId}|null`.
|
|
759
968
|
- **Editing events** — `editstart {row, id, column, columnId, value}`;
|
|
@@ -795,6 +1004,53 @@ horizontally, and multi-select adds a tri-state header checkbox plus Shift+click
|
|
|
795
1004
|
A function is re-called on every render, which is what to use when the placeholder holds controls.
|
|
796
1005
|
<!-- /doc -->
|
|
797
1006
|
|
|
1007
|
+
<!-- doc:grid -->
|
|
1008
|
+
### Grid and `Grid.BillingItems()`
|
|
1009
|
+
|
|
1010
|
+
`Grid` is the public extension point for repeatable data-table presets. It subclasses `Table`
|
|
1011
|
+
without forking its implementation, so sorting, selection, hierarchy, responsive stacking,
|
|
1012
|
+
editable cells/rows, loading, events, and teardown keep the Table contract.
|
|
1013
|
+
|
|
1014
|
+
- **Constructor** — `new Grid(target, tableOptions)` accepts the complete `Table` API.
|
|
1015
|
+
- **Preset** — `Grid.BillingItems(target, options)` supplies an editable hierarchical billing
|
|
1016
|
+
schema for item, quantity, unit, unit price, currency, and line total. It returns a `Grid`.
|
|
1017
|
+
- **Billing options** — `data`, `fields` key overrides, `units`, `currencies`, fallback `currency`,
|
|
1018
|
+
`locale`, `decimals`, `columnOverrides`, complete `columns` replacement, `lineTotal(row, changes)`,
|
|
1019
|
+
and `oneditcommit` in addition to ordinary Table options.
|
|
1020
|
+
- **Rows** — default field names are `id`, `parent`, `kind`, `item`, `quantity`, `unit`,
|
|
1021
|
+
`unitPrice`, `currency`, and `total`. `kind: 'group'|'subtotal'|'header'|'section'` is read-only;
|
|
1022
|
+
other rows are editable. Parent references use Table's flat-data treegrid projection.
|
|
1023
|
+
- **Calculation boundary** — the default line total is quantity × unit price. Applications own
|
|
1024
|
+
taxes, discounts, rounding, subtotal/group recalculation, optimistic persistence, and server
|
|
1025
|
+
validation; provide `lineTotal` or cancel `editcommit` when product policy differs.
|
|
1026
|
+
<!-- /doc -->
|
|
1027
|
+
|
|
1028
|
+
<!-- doc:chart -->
|
|
1029
|
+
### Chart and `ChartJsAdapter`
|
|
1030
|
+
|
|
1031
|
+
An accessible, engine-neutral chart host. Zx renders state, canvas, semantic data summary, tokens,
|
|
1032
|
+
and lifecycle; an injected adapter owns the drawing engine. The Zx runtime imports no Chart.js
|
|
1033
|
+
code, so future adapters do not change the public `Chart` API.
|
|
1034
|
+
|
|
1035
|
+
- **Options** — `adapter`, `type: 'bar'`, `data`, engine-specific `chartOptions`, `label`,
|
|
1036
|
+
`description`, `summary: 'hidden'|'visible'`, `loading`, loading/empty/error text, and
|
|
1037
|
+
`aspectRatio: '16 / 9'`.
|
|
1038
|
+
- **Methods** — `setData()`, `setType()`, `update(chartOptions)`, `setAdapter()`, `setLoading()`,
|
|
1039
|
+
`setError()`, `clearError()`, `resize()`, `refreshTheme()`, `getInstance()`, `destroy()`.
|
|
1040
|
+
- **Events** — `ready {instance}`, `update {data, type}`, `select {datasetIndex, index, dataset,
|
|
1041
|
+
label, value, nativeEvent}`, and `error {error}`.
|
|
1042
|
+
- **Adapter contract** — `adapter.create(canvas, {type, data, options, onSelect})` returns
|
|
1043
|
+
`{update(spec), resize(), destroy(), instance?}`. Implementations must destroy engine resources
|
|
1044
|
+
and must not retain the component after teardown.
|
|
1045
|
+
- **`ChartJsAdapter`** — construct it with a Chart.js constructor or namespace:
|
|
1046
|
+
`new ChartJsAdapter(window.Chart)`. The documentation pins Chart.js 4.5.1 as a development-only
|
|
1047
|
+
asset; `package.json` has no runtime dependencies and application builds explicitly choose how
|
|
1048
|
+
the engine is loaded.
|
|
1049
|
+
- **Accessibility** — the canvas always has a name and optional description, while synchronized
|
|
1050
|
+
labels and dataset values live in a semantic table. `summary: 'hidden'` keeps it visually hidden
|
|
1051
|
+
for assistive technology; `'visible'` makes exact values part of the interface.
|
|
1052
|
+
<!-- /doc -->
|
|
1053
|
+
|
|
798
1054
|
<!-- doc:data-filter -->
|
|
799
1055
|
### DataFilter
|
|
800
1056
|
|
|
@@ -898,6 +1154,76 @@ Beyond the built-in field types, these widget types wrap whole components, used
|
|
|
898
1154
|
`toggle` (Toggle).
|
|
899
1155
|
<!-- /doc -->
|
|
900
1156
|
|
|
1157
|
+
<!-- doc:questionnaire -->
|
|
1158
|
+
### Questionnaire
|
|
1159
|
+
|
|
1160
|
+
A guided, one-question-at-a-time flow: onboarding intake, a service-call checklist, an audit form,
|
|
1161
|
+
a feedback survey. The questionnaire owns the ordered items, the active one, the answers,
|
|
1162
|
+
validation, progress and navigation; the page, card, dialog or drawer around it owns closing,
|
|
1163
|
+
persistence and transport.
|
|
1164
|
+
|
|
1165
|
+
The root is a real `<form>` and the choices are native radios and checkboxes carrying the item
|
|
1166
|
+
name, so `new FormData(questionnaire.toElement())` reads the answers, arrow keys inside a choice
|
|
1167
|
+
group are the browser's rather than ours, and the semantics do not depend on JavaScript. Three
|
|
1168
|
+
things separate it from a `Form` with one `Fieldset` per question: it branches, its answer control
|
|
1169
|
+
can be any registered `Field` type, and its validation may be asynchronous.
|
|
1170
|
+
|
|
1171
|
+
- **Options** — `items: [{name, prompt, description?, section?, required?, multiple?, skippable?,
|
|
1172
|
+
choices?, input?, field?, when?, next?, validate?}]`, `answers: {}` and `active: null` (the
|
|
1173
|
+
resume pair), `progress: true`, `review: false`, `advance: 'manual'|'auto'`, `shortcuts: true`,
|
|
1174
|
+
`allowBack: true`.
|
|
1175
|
+
- **Choices** — `[{value, label, description?, icon?, key?, disabled?, exclusive?}]`. `exclusive`
|
|
1176
|
+
is the "None of the above" behaviour: picking it clears the rest of a `multiple` item, and
|
|
1177
|
+
picking anything else clears it.
|
|
1178
|
+
- **Methods** — `setItems()`, `getItems()`, `goTo(name)`, `next()`, `previous()`, `skip()`,
|
|
1179
|
+
`submit()`, `getAnswers()`, `getAnswer(name)`, `setAnswers(values, {silent})`,
|
|
1180
|
+
`setAnswer(name, value, {silent})`, `getActive()`, `getPath()`, `getProgress()`, `getState()`,
|
|
1181
|
+
`toFormData()`, `reset()`, `focus()`, `destroy()`. `next()`, `skip()` and `submit()` return
|
|
1182
|
+
**promises** — `next()` because an item's `validate` may be asynchronous, and `skip()` because
|
|
1183
|
+
skipping the last question submits. Everything else is synchronous.
|
|
1184
|
+
- **Events** — `change {name, value, answers}`, `navigate {from, to, reason}` **preventable**,
|
|
1185
|
+
`skip {name}`, `invalid {name, message}`, `submit {answers, path}` **preventable**,
|
|
1186
|
+
`complete {answers, path}`.
|
|
1187
|
+
- **Branching** — `when(answers)` decides whether an item is asked at all, and `next` (a name, or
|
|
1188
|
+
`(answer, answers) => name`) decides what follows. Conditions **cascade**: each predicate sees
|
|
1189
|
+
only the answers of visible items declared before it, so hiding the VAT question also hides
|
|
1190
|
+
whatever depended on the VAT id. An answer to a conditioned-out question is kept internally —
|
|
1191
|
+
walk back into the branch and it is still there — but is left out of `getAnswers()` and
|
|
1192
|
+
`toFormData()`, so an abandoned branch never submits stale data. A `next` naming a hidden item
|
|
1193
|
+
walks forward from it rather than stranding the reader; naming an item that does not exist
|
|
1194
|
+
throws `RangeError`.
|
|
1195
|
+
- **Path and progress** — `getPath()` is the questionnaire's memory of the route walked, and
|
|
1196
|
+
`previous()` pops it: once a branch has been taken, "previous" is not `index - 1`. `getProgress()`
|
|
1197
|
+
returns `{index, answered, total, percent, section}` where `total` is the path plus the items
|
|
1198
|
+
still reachable — exact for a static flow, an estimate once a function `next` can jump somewhere
|
|
1199
|
+
the forward walk cannot predict.
|
|
1200
|
+
- **Field answers** — `field: {type: 'date'|'number'|'zxselect'|'rating'|'upload'|…}` hands the
|
|
1201
|
+
answer to a `Field`, so any registered field type works, including ones an application registered
|
|
1202
|
+
itself. The `<legend>` already asks the question, so the Field's own label is hidden from sight
|
|
1203
|
+
but kept as the control's accessible name unless the item gives `field.label` explicitly; the
|
|
1204
|
+
Field draws its own highlight, so a failing answer shows one message, not two.
|
|
1205
|
+
- **Validation** — `required` is checked synchronously; an item's `validate(answer, answers)` may
|
|
1206
|
+
return a message, `null`, or a **promise** of either, and a rejection is reported as its message.
|
|
1207
|
+
While the check is pending the root carries `data-state="checking"` and `aria-busy="true"` and
|
|
1208
|
+
the actions are disabled.
|
|
1209
|
+
- **Review** — `review: true` shows a summary of every answer on the path before submitting, each
|
|
1210
|
+
with an Edit that reopens that question; Continue from an edited question comes straight back to
|
|
1211
|
+
the review rather than replaying the tail.
|
|
1212
|
+
- **Semantics** — the item is a `<fieldset>` and the prompt its `<legend>`; the description and the
|
|
1213
|
+
error are associated through `aria-describedby`, the error is a `role="alert"`, and a failing
|
|
1214
|
+
item carries `aria-invalid="true"`. `data-state` on the root is
|
|
1215
|
+
`asking`, `checking`, `review`, or `complete`. Submitting does not replace the content: it sets
|
|
1216
|
+
`data-state="complete"`, hides the actions, and emits `complete`, leaving the thank-you screen to
|
|
1217
|
+
the application.
|
|
1218
|
+
- **Keyboard** — `1`–`9` then `a`–`z` pick the matching answer, assigned in choice order (an
|
|
1219
|
+
explicit `key` is reserved first, so adding one never renumbers the others) and shown as a key
|
|
1220
|
+
cap on each choice; the caps are `aria-hidden`, since a screen-reader user moves through the
|
|
1221
|
+
group with the arrow keys the radios already provide. `Enter` continues, except in a `multiline`
|
|
1222
|
+
input. Shortcuts are suppressed while focus is in a text control. Navigating moves focus to the
|
|
1223
|
+
new question's fieldset, which is what gets the prompt announced; failing validation moves it to
|
|
1224
|
+
the first answer control instead.
|
|
1225
|
+
<!-- /doc -->
|
|
1226
|
+
|
|
901
1227
|
<!-- doc:value-list -->
|
|
902
1228
|
### ValueList
|
|
903
1229
|
|
|
@@ -917,9 +1243,13 @@ or Ctrl+Arrow reorders.
|
|
|
917
1243
|
Ordered value editor with explicit rows (add / remove / move).
|
|
918
1244
|
|
|
919
1245
|
- **Options** — `values`, `options` (allowed values, which turns the rows into selects),
|
|
920
|
-
`addLabel
|
|
1246
|
+
`addLabel`, `reorder: 'both'` (`'both'` | `'buttons'` | `'drag'` | `'none'` — a drag handle, the
|
|
1247
|
+
up/down buttons, both, or neither).
|
|
921
1248
|
- **Methods** — `getValues()`, `setValues()`.
|
|
922
1249
|
- **Events** — `change {values}`.
|
|
1250
|
+
- Dragging goes through a dedicated handle, so text selection inside a row's input still works. The
|
|
1251
|
+
handle is also a keyboard control (Arrow keys, Home, End), which is what keeps `reorder: 'drag'`
|
|
1252
|
+
operable without a pointer.
|
|
923
1253
|
<!-- /doc -->
|
|
924
1254
|
|
|
925
1255
|
<!-- doc:field-upload -->
|
|
@@ -958,17 +1288,6 @@ yet. Field type: `tagpicker`.
|
|
|
958
1288
|
within the list.
|
|
959
1289
|
<!-- /doc -->
|
|
960
1290
|
|
|
961
|
-
<!-- doc:permission -->
|
|
962
|
-
### Permission
|
|
963
|
-
|
|
964
|
-
Private / public / group record-permission selector.
|
|
965
|
-
|
|
966
|
-
- **Options** — `value: true|false|groupId`, `groups: []`, `groupsValueKey: 'ID'`,
|
|
967
|
-
`groupsLabelKey: 'name'`.
|
|
968
|
-
- **Methods** — `get()` → `'private'|'public'|groupId`, `set(value)`.
|
|
969
|
-
- **Events** — `change {value}`.
|
|
970
|
-
<!-- /doc -->
|
|
971
|
-
|
|
972
1291
|
<!-- doc:groupbox -->
|
|
973
1292
|
### Groupbox
|
|
974
1293
|
|
|
@@ -1038,6 +1357,62 @@ markup with no JavaScript.
|
|
|
1038
1357
|
because a CSS custom property cannot be used inside a media query.
|
|
1039
1358
|
<!-- /doc -->
|
|
1040
1359
|
|
|
1360
|
+
<!-- doc:dock -->
|
|
1361
|
+
### Dock
|
|
1362
|
+
|
|
1363
|
+
A stack of collapsible, resizable panes: the docked inspector column of a design tool, and the
|
|
1364
|
+
detail side of a master–detail screen. Give the root a size of its own (a grid area, `block-size:
|
|
1365
|
+
100%`, a flex child) when the panes should fill a shell rather than grow with their content.
|
|
1366
|
+
|
|
1367
|
+
- **Options** — `orientation: 'vertical'`, `content: null`, `panes: []`, `resizable: true`,
|
|
1368
|
+
`collapsible: true`, `lazy: true`, `storageKey: null`.
|
|
1369
|
+
- **Pane** — `{name, title?, content?, tabs?, active?, size?, min?, grow?, collapsed?,
|
|
1370
|
+
collapsible?, side?}`. `content` takes a string, Node, Component, or a factory called on first
|
|
1371
|
+
reveal. A pane with `tabs` renders its strip in place of the title; `content` is then ignored.
|
|
1372
|
+
`side` (`'start' | 'end'`) only applies when the dock has a `content`.
|
|
1373
|
+
- **Tab** — `{name, title, content?}`, same content shapes.
|
|
1374
|
+
- **Methods** — `pane(name)`, `names()`, `add(pane, {index})`, `remove(name)`,
|
|
1375
|
+
`collapse(name)`, `expand(name)`, `toggle(name)`, `isCollapsed(name)`, `reveal(name)`,
|
|
1376
|
+
`setSize(name, size)`, `getSize(name)`, `activate(pane, tab)`, `getActive(pane)`,
|
|
1377
|
+
`state()`, `setState(state)`, `adopt(sheet, options)`, `release(sheet)`.
|
|
1378
|
+
- **Adopting a Sheet** — `adopt(sheet, {name?, side?, index?, size?, min?, dockAt?})` takes over a
|
|
1379
|
+
`Sheet`'s positioning: it becomes a track in the dock rather than an overlay. The dock owns
|
|
1380
|
+
where it sits, its size, and its resizing; the sheet keeps its content, header, footer, events,
|
|
1381
|
+
and its own `open()`/`close()`. `dockAt` names a breakpoint below which the dock hands the sheet
|
|
1382
|
+
back to a free overlay and reclaims the track, re-adopting it above — measured on the **dock's
|
|
1383
|
+
own width**, so a dock inside a split pane behaves correctly where a viewport media query would
|
|
1384
|
+
not. Because a dock is a flex container by construction it is always a valid host, which is why
|
|
1385
|
+
docking needs no arrangement from the application.
|
|
1386
|
+
- **Ownership** — the dock never owns an adopted sheet: `destroy()` releases them rather than
|
|
1387
|
+
destroying them, `remove(name)` on a sheet releases it, and a sheet that destroys itself while
|
|
1388
|
+
adopted drops out of the dock cleanly.
|
|
1389
|
+
- **Events** — `collapse {name}`, `expand {name}`, `resize {name, size}`,
|
|
1390
|
+
`tabchange {pane, tab, previous}`, `reveal {name}`.
|
|
1391
|
+
- **Sizing** — one custom property per pane and one flex rule: a sized pane is
|
|
1392
|
+
`flex: 0 0 var(--zx-pane-size)`, a collapsed one falls to `flex: 0 0 auto` with its body hidden,
|
|
1393
|
+
and exactly one element grows. With a `content` that is always the content; without one it is the
|
|
1394
|
+
pane declaring `grow`, or the last expanded pane. A drag therefore rewrites at most two
|
|
1395
|
+
properties, and collapsing a pane feeds the grower rather than redistributing across the stack.
|
|
1396
|
+
- **Regions** — with `content` the dock is a region rather than a plain stack: panes sit on either
|
|
1397
|
+
`side` of the flexible middle. Nesting one dock inside another dock's pane gives a workbench —
|
|
1398
|
+
a canvas with an inspector column beside it.
|
|
1399
|
+
- **`reveal(name)`** — accepts a pane name or a tab name. Expands whichever pane holds it,
|
|
1400
|
+
activates the tab when the name is a tab's, and scrolls it into view.
|
|
1401
|
+
- **Persistence** — `storageKey` remembers sizes, collapsed panes, and active tabs through
|
|
1402
|
+
`storage()`, which degrades to memory rather than throwing. `state()`/`setState()` expose the
|
|
1403
|
+
same shape for storing it elsewhere; unknown pane names are ignored, so a stored layout survives
|
|
1404
|
+
a release that added or removed panes.
|
|
1405
|
+
- **Keyboard and screen readers** — each pane header is a row, not one big button, so the collapse
|
|
1406
|
+
toggle and the tab strip can coexist; the toggle carries `aria-expanded` and `aria-controls`.
|
|
1407
|
+
Tabs follow the APG tab pattern with one roving tab stop, Left/Right moving between them.
|
|
1408
|
+
Dividers are focusable `separator`s carrying live `aria-valuenow`/`aria-valuemin`/`aria-valuemax`
|
|
1409
|
+
in pixels: Up/Down (or Left/Right when horizontal) resize by 16px, Shift by 64px, and Enter or
|
|
1410
|
+
Space collapses the pane before the divider. A divider with nothing to resize is taken out of the
|
|
1411
|
+
tab order.
|
|
1412
|
+
- **Out of scope** — drag-to-rearrange, tear-off panels, and multi-column docks. All three are
|
|
1413
|
+
additive without changing the API above.
|
|
1414
|
+
<!-- /doc -->
|
|
1415
|
+
|
|
1041
1416
|
<!-- doc:tabbox -->
|
|
1042
1417
|
### Tabbox — APG tabs
|
|
1043
1418
|
|
|
@@ -1058,8 +1433,13 @@ restores rounded corners without forking the stylesheet.
|
|
|
1058
1433
|
- **Methods** — `addTab()`, `removeTab()`, `openTab(name)`, `getActive()`, `setTitle()`,
|
|
1059
1434
|
`setBadge(name, text|null)`, `enableTab()`/`disableTab()`.
|
|
1060
1435
|
- **Events** — `change {name, previous}` (preventable), `close {name}`.
|
|
1436
|
+
- **Behaviour** — a `closable` tab renders a × that closes it on click, and `close` fires for that
|
|
1437
|
+
gesture just as it does for Delete. `removeTab()` is the silent programmatic path. Closing the
|
|
1438
|
+
active tab activates its neighbour through the usual `change` event, so a `preventDefault()` on
|
|
1439
|
+
that change also vetoes the close.
|
|
1061
1440
|
- **Keyboard** — ArrowLeft/Right and Home/End move focus, Enter/Space activate, Delete closes a
|
|
1062
|
-
closable tab.
|
|
1441
|
+
closable tab. The × is a plain glyph rather than a nested button (buttons cannot nest), so
|
|
1442
|
+
keyboard and screen-reader users close through Delete; focus lands on the neighbouring tab.
|
|
1063
1443
|
<!-- /doc -->
|
|
1064
1444
|
|
|
1065
1445
|
<!-- doc:stepper -->
|
|
@@ -1068,7 +1448,9 @@ restores rounded corners without forking the stylesheet.
|
|
|
1068
1448
|
Linear progress through a multi-step flow: a numbered `<ol>` where each step is upcoming, active,
|
|
1069
1449
|
complete, or errored. It promotes the markup the checkout and record wizards used to hand-roll.
|
|
1070
1450
|
Advancing past a step marks it complete automatically, and only steps the `clickable` policy allows
|
|
1071
|
-
are rendered as real buttons — the rest are not focusable at all.
|
|
1451
|
+
are rendered as real buttons — the rest are not focusable at all. A horizontal rail fills the width
|
|
1452
|
+
it is given: the labels keep their own width and the connectors stretch to take the slack, wrapping
|
|
1453
|
+
onto more lines only when the container is too narrow to hold them.
|
|
1072
1454
|
|
|
1073
1455
|
- **Options** — `steps: [{name, title, description?, optional?, disabled?}]`, `active` (defaults to
|
|
1074
1456
|
the first enabled step), `orientation: 'horizontal'|'vertical'`, `clickable:
|
|
@@ -1136,12 +1518,67 @@ menu on narrow widths.
|
|
|
1136
1518
|
- **Events** — `change {name}`.
|
|
1137
1519
|
<!-- /doc -->
|
|
1138
1520
|
|
|
1521
|
+
<!-- doc:app-sidebar -->
|
|
1522
|
+
### AppSidebar — expanded application navigation
|
|
1523
|
+
|
|
1524
|
+
Persistent vertical application navigation with header, scrollable tree, and sticky footer slots.
|
|
1525
|
+
It is route-agnostic: ZeyOS supplies native `href` values or handles cancelable selections through
|
|
1526
|
+
`PG.go()`. This expanded presentation is the only application-nav component that reveals children
|
|
1527
|
+
inline.
|
|
1528
|
+
|
|
1529
|
+
- **Options** — shared application `items`, `active`, `expanded`, `collapsed`, `side: 'left'|'right'`,
|
|
1530
|
+
`label`, and `header`, `footer`, `railHeader`, `railFooter` content slots.
|
|
1531
|
+
- **Methods** — `setItems()`, `setActive()`, `setExpanded()`, `getExpanded()`, `toggleBranch()`,
|
|
1532
|
+
`expand()`, `collapse()`, `setCollapsed()`, `isCollapsed()`, `getRail()`, `destroy()`.
|
|
1533
|
+
- **Events** — cancelable `select {item, id, value, href, event}`,
|
|
1534
|
+
`branchchange {item, id, expanded, ids}` (`item`, `id`, and `expanded` are null for a bulk
|
|
1535
|
+
`setExpanded()`), and `collapsechange {collapsed}`.
|
|
1536
|
+
- **Minimized state** — collapse composes `AppRail` over the same normalized item tree. Expanded
|
|
1537
|
+
branch IDs survive the round trip but are not rendered inline while minimized.
|
|
1538
|
+
- **Semantics** — persistent navigation uses `<nav>` and nested lists with native links and
|
|
1539
|
+
disclosure buttons, not menu or menubar roles. A parent with children is disclosure-only; give
|
|
1540
|
+
it an explicit Overview child when the parent also needs a destination.
|
|
1541
|
+
<!-- /doc -->
|
|
1542
|
+
|
|
1543
|
+
<!-- doc:app-rail -->
|
|
1544
|
+
### AppRail — minimized application navigation
|
|
1545
|
+
|
|
1546
|
+
The compact form of application navigation for left, right, top, or bottom placement. It can flow
|
|
1547
|
+
vertically or horizontally, but descendants are always anchored flyouts—never inline—and open
|
|
1548
|
+
toward the workspace.
|
|
1549
|
+
|
|
1550
|
+
- **Options** — shared application `items`, `active`, `orientation: 'vertical'|'horizontal'`,
|
|
1551
|
+
`side: 'left'|'right'|'top'|'bottom'`, `label`, `header`, `footer`, `openDelay: 80`, and
|
|
1552
|
+
`closeDelay: 160`.
|
|
1553
|
+
- **Methods** — `setItems()`, `setActive()`, `getItems()`, `openFlyout(id, {focus})`,
|
|
1554
|
+
`closeFlyout()`, `closeAllFlyouts()` (including pending hover/focus timers and descendant
|
|
1555
|
+
branches), `isFlyoutOpen()`, `focus()`, `destroy()`.
|
|
1556
|
+
- **Events** — cancelable `select {item, id, value, href, event}` and
|
|
1557
|
+
`flyoutchange {item, id, open}`.
|
|
1558
|
+
- **Pointer and focus** — hover or focus reveals a child collection without stealing focus. The
|
|
1559
|
+
close delay covers the trigger-to-panel gap; leaving both trigger and panel closes it. Click,
|
|
1560
|
+
Enter, Space, and the inward arrow also open it, so horizontal subnavigation is discoverable and
|
|
1561
|
+
usable without hover. Escape, outside activation, or selection closes it.
|
|
1562
|
+
- **Semantics** — every icon-only destination retains a visible Tooltip and an accessible name.
|
|
1563
|
+
Persistent rail destinations remain native links/buttons in a labelled navigation list; the
|
|
1564
|
+
transient child flyout supplies its own arrow-key focus interaction.
|
|
1565
|
+
<!-- /doc -->
|
|
1566
|
+
|
|
1139
1567
|
<!-- doc:kernel -->
|
|
1140
1568
|
### Core helpers
|
|
1141
1569
|
|
|
1142
1570
|
- **Component** — the base class: `on`/`off`/`once`/`emit`, `listen`, `toElement`, `msg`,
|
|
1143
1571
|
`destroy`, and the static `Component.from(el)`.
|
|
1144
|
-
- **DOM** — `h(tag, props, ...children)`, `h.raw(html)`, `htmlEscape`, `resolveElement`.
|
|
1572
|
+
- **DOM** — `h(tag, props, ...children)`, `h.raw(html)`, `htmlEscape`, `resolveElement`. The current
|
|
1573
|
+
ZeyOS builder is also first-class: `__(tag#id.class, properties, content)` is the migration alias
|
|
1574
|
+
for `compactElement()`. It preserves `D*` data, `S*` style, boolean/built-in property, attribute,
|
|
1575
|
+
and legacy `on*` callback rules while keeping content text-safe. `applyCompactProperties`,
|
|
1576
|
+
`appendContent`, `appendCompact` (parent return), `appendCompactChild` (child return), and
|
|
1577
|
+
`fragment(...children)` are the pure forms.
|
|
1578
|
+
- **Compact events** — `onCompactEvent`, `offCompactEvent`, and `fireCompactEvent` share one
|
|
1579
|
+
newest-first handler registry. Existing `DOM.on/remEvt/fireEvt` methods can delegate to this
|
|
1580
|
+
triad. Zx never installs globals or alters DOM prototypes; the current application owns those
|
|
1581
|
+
temporary seams while `UI.new*` implementations are replaced without rewriting `PG` routing.
|
|
1145
1582
|
- **Icons** — `icon(name, {size, label})` and `icons`; see the Icons section — bundled inline SVG
|
|
1146
1583
|
by default, Font Awesome after an opt-in.
|
|
1147
1584
|
- **Positioning** — `position(anchor, floating, {placement, offset, flip, matchWidth})` →
|
|
@@ -1286,8 +1723,9 @@ never imported by `src/index.js`, and adds nothing to an application that does n
|
|
|
1286
1723
|
|
|
1287
1724
|
- **Loading** — classic: `<script src="/assets/zx-compat.global.js">` → `window.gx`; module:
|
|
1288
1725
|
`import { gx } from '/assets/zx-compat.esm.js'` (or `src/compat/index.js` in-repo).
|
|
1289
|
-
`gx.compat.installGlobals()` additionally installs the
|
|
1290
|
-
`String.htmlSpecialChars`, and the element `store`/`retrieve` shim.
|
|
1726
|
+
`gx.compat.installGlobals()` additionally installs the gx `__()` parser only when that name is
|
|
1727
|
+
free, plus `_()`, `String.htmlSpecialChars`, and the element `store`/`retrieve` shim. It never
|
|
1728
|
+
replaces ZeyOS's compact builder. `gx.install(host)` assigns
|
|
1291
1729
|
the namespace without the optional globals.
|
|
1292
1730
|
- **`gx.zeyos`** — Select/SelectFilter/SelectDyn/SelectPrio, Table, Tabbox, Panel, MasterPanel,
|
|
1293
1731
|
Groupbox, Search, Datebox, DatePicker, MonthPicker, TimePicker, Timebox, Toggle, Msgbox, Popup,
|