@zeyos/zx 5.0.0 → 5.2.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 +164 -0
- package/README.md +2 -1
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +9 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +288 -14
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- package/dist/types/components/dropdown/dropdown.d.ts +22 -7
- package/dist/types/components/field/field.d.ts +30 -0
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +7 -0
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/launcher/launcher.d.ts +8 -0
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +15 -6
- package/dist/types/components/menu-button/menu-button.d.ts +6 -0
- package/dist/types/components/message/message.d.ts +14 -1
- package/dist/types/components/modal/modal.d.ts +30 -0
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- package/dist/types/components/select/select.d.ts +6 -0
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/table/table.d.ts +265 -17
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/overlay-host.d.ts +28 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- package/dist/types/zeyos/index.d.ts +1 -0
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +31 -5
- package/dist/types/zeyos/table.d.ts +245 -8
- package/dist/types/zeyos/view.d.ts +16 -2
- package/dist/zx-zeyos.esm.js +4422 -430
- package/dist/zx-zeyos.esm.min.js +5 -5
- package/dist/zx.css +1417 -134
- package/dist/zx.esm.js +6986 -830
- package/dist/zx.esm.min.js +8 -8
- package/dist/zx.global.js +6986 -830
- package/dist/zx.global.min.js +8 -8
- package/dist/zx.min.css +1 -1
- package/docs/api.json +794 -17
- package/docs/llms.md +503 -63
- package/docs/llms.txt +6 -2
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-list/activity-list.js +27 -1
- package/src/components/app-icon/app-icon.css +13 -2
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +19 -0
- package/src/components/app-sidebar/app-sidebar.css +204 -3
- package/src/components/app-sidebar/app-sidebar.js +646 -40
- package/src/components/breadcrumb/breadcrumb.css +1 -1
- package/src/components/calendar/calendar.css +1 -1
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +2 -2
- package/src/components/card-view/card-view.css +5 -5
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -3
- package/src/components/code-editor/code-editor.css +1 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-input.css +1 -1
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/time-picker.css +2 -2
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.js +157 -72
- package/src/components/field/field.js +33 -3
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.js +10 -59
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +300 -8
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +1 -1
- package/src/components/kanban-view/kanban-view.css +3 -3
- package/src/components/kanban-view/kanban-view.js +5 -2
- package/src/components/launcher/launcher.css +1 -1
- package/src/components/launcher/launcher.js +29 -3
- package/src/components/list-toolbar/list-toolbar.js +39 -13
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +63 -0
- package/src/components/menu-button/menu-button.js +97 -11
- package/src/components/message/message.js +60 -20
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +66 -13
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/number-field/field-adapter.js +2 -1
- package/src/components/pagination/pagination.css +5 -5
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +3 -2
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +1 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/select.js +9 -0
- package/src/components/sheet/sheet.css +1 -1
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +2 -2
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +1 -1
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +9 -5
- package/src/components/table/table.css +86 -11
- package/src/components/table/table.js +628 -57
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tooltip/tooltip.js +35 -17
- package/src/components/tree/tree.css +1 -1
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +12 -2
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/overlay-host.js +64 -9
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +5 -2
- package/src/internal/app-rail.js +134 -53
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/form.js +5 -3
- package/src/zeyos/index.js +3 -0
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +44 -13
- package/src/zeyos/table.js +220 -80
- package/src/zeyos/view.js +43 -93
- package/styles/base.css +16 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +56 -28
- package/styles/zx.css +3 -0
package/docs/llms.md
CHANGED
|
@@ -61,7 +61,10 @@ Themes: `light` | `dark` | `auto` (follows OS).
|
|
|
61
61
|
(`--zx-space-unit`, 4px: `--zx-space-1…8` = 4/8/12/16/20/24/32/40px), radius steps are fractions of
|
|
62
62
|
one base (`--zx-radius`, 4px: `-sm/-md/-lg/-xl` = 2/3/4/6px; `-full` only for shapes whose roundness
|
|
63
63
|
is the meaning — badges, avatars, toggles), and controls are `--zx-control-height` (30px) tall.
|
|
64
|
-
To tighten or loosen an application, override those tokens on `:root` or on any region
|
|
64
|
+
To tighten or loosen an application, override those tokens on `:root` or on any region. The
|
|
65
|
+
geometry and material tokens (control size, radius, spacing, blur and glass filters, durations,
|
|
66
|
+
easings, the type scale) are declared on `:root` only, never on a `[data-zx-theme]` block, so an
|
|
67
|
+
override on `:root` reaches nested light and dark regions alike; theme blocks carry colours only:
|
|
65
68
|
|
|
66
69
|
```css
|
|
67
70
|
:root { --zx-space-unit: 3.5px; --zx-control-height: 28px; }
|
|
@@ -114,7 +117,9 @@ accent), `--zx-color-bg-chrome` (navigation and inspector columns, one tone off
|
|
|
114
117
|
`--zx-color-on-accent-solid` (the one filled primary button per screen),
|
|
115
118
|
`--zx-color-danger/warning/success/info(+ -bg)`, the categorical tone palette
|
|
116
119
|
`--zx-color-tone-{blue,teal,green,amber,orange,red,pink,violet,slate}-bg/-fg` (tags, chips, icon
|
|
117
|
-
tiles, avatars — neither the accent nor a status),
|
|
120
|
+
tiles, avatars — neither the accent nor a status) and `-solid` (a solid identity fill, such as a
|
|
121
|
+
module tile, that the white icon glyph reads on at 3:1 in both themes), the chart series
|
|
122
|
+
`--zx-color-chart-1…8`,
|
|
118
123
|
`--zx-focus-ring`, `--zx-control-height/-radius`, `--zx-space-*`, `--zx-radius-*`, `--zx-text-*`
|
|
119
124
|
(`-3xl` for document titles), `--zx-weight-medium/-semibold`, `--zx-tracking-tight/-title`. Never
|
|
120
125
|
use tier-1 palette tokens (`--zx-gray-*`, `--zx-green-*`, `--zx-emerald-*`, `--zx-tone-*`) or raw
|
|
@@ -220,12 +225,20 @@ await records.saveView({ name: 'My open invoices', setDefault: true });
|
|
|
220
225
|
behavior. `load` maps stored timestamps to `Date`; `save` validates and creates/updates, maps back
|
|
221
226
|
to Unix seconds, calls `onSaved`, and shows a success toast. Its toasts (that confirmation and a
|
|
222
227
|
failed request's report) appear in the theme scope of `scope` — default: the form's own element,
|
|
223
|
-
so a form in a scoped dialog toasts inside that scope
|
|
228
|
+
so a form in a scoped dialog toasts inside that scope, and the confirmation stays there when
|
|
229
|
+
`onSaved` closes and destroys the dialog; `notify: false` raises none, leaving the
|
|
224
230
|
promise's result or rejection to the caller.
|
|
225
231
|
- `zeyosTable(client, resource, opts)` returns
|
|
226
|
-
`{table, load(), setSearch(), setFilters(), loadMore(), count, page, hasMore, destroy()}`.
|
|
227
|
-
come from schema metadata, sorting uses `sortMode:'server'`, and
|
|
228
|
-
|
|
232
|
+
`{table, load(), setSearch(), setFilters(), loadMore(), count, page, pageSize, hasMore, destroy()}`.
|
|
233
|
+
Columns come from schema metadata, sorting uses `sortMode:'server'`, and paging is zero-based with
|
|
234
|
+
`pageSize: 50` by default: page zero replaces rows while later pages append them using server
|
|
235
|
+
`limit`/`offset` and `count`; `load({page, pageSize, replace: true})` replaces at any page. A
|
|
236
|
+
one-based `Pagination` maps onto it as `load({page: detail.page - 1, pageSize: detail.pageSize,
|
|
237
|
+
replace: true})` (offset `(page - 1) * pageSize`), feeding `result.count` back through
|
|
238
|
+
`setTotal()`. A response that resolves after `destroy()` is ignored and later loads reject; page
|
|
239
|
+
must be a non-negative integer and page size a positive integer (`RangeError`), filters an object
|
|
240
|
+
(`TypeError`), and every argument is checked before any is applied, so a rejected call changes
|
|
241
|
+
nothing. Table presentation options and
|
|
229
242
|
listeners pass through — `variant`, `zebra`, `stickyHeader`, `responsive`, `growing`, `emptyText`,
|
|
230
243
|
`rowClass`, `rowKind(s)`, `columnVisibility`, `columnReorder`, `hiddenColumns`, `on<event>`;
|
|
231
244
|
`onrowclick` runs beside `onRowClick`. The binding owns `columns`, `data`, and `sortMode`, and
|
|
@@ -234,7 +247,8 @@ await records.saveView({ name: 'My open invoices', setDefault: true });
|
|
|
234
247
|
complete projection, and view options. `zeyosView(client, resource, CardView|TableView|KanbanView,
|
|
235
248
|
opts)` returns `{view, load(), setSearch(), setFilters(), loadMore(), count, page, hasMore,
|
|
236
249
|
savedViews, ready, restoreDefault(), captureView(), saveView(), applyView(), destroy()}` with
|
|
237
|
-
paging and server sort.
|
|
250
|
+
paging and server sort. Paging is the same loader as `zeyosTable()` — same `pageSize: 50` default,
|
|
251
|
+
validation, `replace`/`pageSize` load options, and destroy guard. Title, preview, group, column, and swim-lane fields stay in the projection
|
|
238
252
|
even when hidden. Without `savedViews`, the host may persist `view.getViewState()` directly.
|
|
239
253
|
- `createSavedViewRegistry(transport, {userId,workspaceId,resource})` coordinates private named
|
|
240
254
|
Table/Card/Kanban configurations as one versioned, atomically replaced document. Entries capture
|
|
@@ -264,8 +278,10 @@ instance's zone — a day, not an instant: tables show it with no time, and form
|
|
|
264
278
|
so neither the viewer's zone nor daylight saving moves it. Pass the instance's IANA zone as
|
|
265
279
|
`timeZone` (table, form, view; default: the runtime's own); `unixSecondsToCalendarDay(seconds, tz)`
|
|
266
280
|
and `calendarDayToUnixSeconds(date, tz)` do the same conversion for application code. Use
|
|
267
|
-
`dataFilterStateToFilters(state, defs)` before `tableBinding.setFilters(...)` when wiring
|
|
268
|
-
`DataFilter
|
|
281
|
+
`dataFilterStateToFilters(state, defs, {timeZone})` before `tableBinding.setFilters(...)` when wiring
|
|
282
|
+
a `DataFilter`: with `timeZone`, a `format: 'date'` value (`Date` or `YYYY-MM-DD`) becomes that
|
|
283
|
+
day's midnight in the zone, as forms store it, while `datetime` values stay instants; without it,
|
|
284
|
+
dates convert as exact instants. `FilterPanel` takes the same `timeZone` option.
|
|
269
285
|
|
|
270
286
|
### Module icons and colours
|
|
271
287
|
|
|
@@ -303,6 +319,41 @@ registerModules({ tickets: '#f04639', 'my-fork': { label: 'My Fork', icon: 'zeyo
|
|
|
303
319
|
throw.
|
|
304
320
|
- `registerModules(map)` adds forks and weblets or overrides shipped entries (a bare string sets
|
|
305
321
|
the colour only). Runtime ZeyOS menu data is authoritative: feed its colours in at startup.
|
|
322
|
+
|
|
323
|
+
### Module bar from the ZeyOS menu
|
|
324
|
+
|
|
325
|
+
The module bar and the module-layout sidebar read the same shell state the launcher does, plus
|
|
326
|
+
each module's menu and its counts:
|
|
327
|
+
|
|
328
|
+
```js
|
|
329
|
+
import { buildZeyosAppSidebarConfig, buildZeyosModuleBarConfig } from '/assets/zx-zeyos.esm.js';
|
|
330
|
+
import { AppSidebar, ModuleBar } from '/assets/zx.esm.js';
|
|
331
|
+
|
|
332
|
+
const state = {
|
|
333
|
+
modules: ['messages', 'tasks', 'billing', 'contacts', 'calendar', 'tickets'],
|
|
334
|
+
pinned: ['messages', 'tasks', 'billing', 'tickets'], // the rest are under "All modules"
|
|
335
|
+
activeIdentifier: 'tickets',
|
|
336
|
+
active: 'tickets-mine', // a menu entry, when more precise
|
|
337
|
+
canUseModule: (module) => permissions.has(module),
|
|
338
|
+
menus: { tickets: [{ id: 'tickets-mine', label: 'My tickets' }, ['tickets-all', 'All tickets', '#tickets']] },
|
|
339
|
+
badges: { messages: 7, 'tickets-mine': 3 },
|
|
340
|
+
resolveApplication: (app) => ({ href: routeFor(app) })
|
|
341
|
+
};
|
|
342
|
+
new AppSidebar(nav, buildZeyosAppSidebarConfig(state, { onselect: route }));
|
|
343
|
+
new ModuleBar(header, buildZeyosModuleBarConfig(state, { orientation: 'horizontal', side: 'top' }));
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- `zeyosModuleItems(state, {standardIcons})` → `ModuleItem[]`: every permitted module, fork, and
|
|
347
|
+
weblet, with its kit icon (or `renderApplicationIcon(app, normalized)`), its colour — the
|
|
348
|
+
payload's, else the registry's, so `registerModules()` reaches the bar — its menu from
|
|
349
|
+
`menus[identifier]` (objects or `[id, label, href]` arrays, sections, nesting; unsafe hrefs are
|
|
350
|
+
disabled), its badge from `badges`, `pinned` from the pinned order, and forks and weblets in
|
|
351
|
+
their own catalogue group (`labels.forks`, `labels.weblets`). A module with a menu is a panel
|
|
352
|
+
module; one without is a destination.
|
|
353
|
+
- `buildZeyosModuleBarConfig(state, options)` → ModuleBar options; `buildZeyosAppSidebarConfig()`
|
|
354
|
+
→ AppSidebar options in `layout: 'modules'`. Both draw the classic ZeyOS identity tiles
|
|
355
|
+
(`appearance: 'solid'`) unless told otherwise. `zeyosModuleBar(target, state, options)` creates
|
|
356
|
+
the bar directly.
|
|
306
357
|
<!-- /doc -->
|
|
307
358
|
|
|
308
359
|
## Custom elements
|
|
@@ -384,8 +435,10 @@ a `MenuButton`, where choosing one activates the original control.
|
|
|
384
435
|
`setActive(name, active?)`, `destroy()`.
|
|
385
436
|
- **Events** — `action {name, item}`, emitted for descriptor-built items in addition to their own
|
|
386
437
|
`onclick`, from the row and from the overflow menu alike.
|
|
387
|
-
- **Keyboard** — Tab reaches the toolbar once; ArrowLeft/ArrowRight move between controls and wrap
|
|
388
|
-
Home/End jump to the first and last control.
|
|
438
|
+
- **Keyboard** — Tab reaches the toolbar once; ArrowLeft/ArrowRight move between controls and wrap,
|
|
439
|
+
in visual order under `dir="rtl"`; Home/End jump to the first and last control. A text field, select,
|
|
440
|
+
or contenteditable item keeps its own keys: Home/End and the arrows move its caret, and an arrow
|
|
441
|
+
moves on to the neighbouring control only once the caret is at that edge of the text.
|
|
389
442
|
- **Pressed items** — an item with `active` renders `aria-pressed="true"` and takes the neutral
|
|
390
443
|
`--zx-color-bg-current` fill, never the accent.
|
|
391
444
|
<!-- /doc -->
|
|
@@ -524,7 +577,9 @@ Zx owns ranking, focus, request cancellation, and presentation; ZeyOS owns the c
|
|
|
524
577
|
permissions, recent-history/cache policy, and the route or command ultimately invoked.
|
|
525
578
|
|
|
526
579
|
- **Constructor** — `new Launcher(null, options)` owns a top-layer dialog in the opener's nearest
|
|
527
|
-
Zx theme scope
|
|
580
|
+
Zx theme scope — never inside another open dialog, which it stacks above in the top layer. Open,
|
|
581
|
+
it is a modal on the shared overlay stack: Esc and presses inside it reach no popup left open
|
|
582
|
+
behind it, and toasts move into it while it is open;
|
|
528
583
|
`new Launcher(existingDialog, options)` enhances and later restores an existing `<dialog>`.
|
|
529
584
|
- **Options** — `items`, abortable `sources: [{id, label?, minQuery?, when?, order?, load(query, {signal})}]`,
|
|
530
585
|
`query`, `debounce: 250`, `minQuery: 0`, `maxResults: 100`, `placeholder`, `label`, `emptyText`,
|
|
@@ -584,7 +639,9 @@ A fixed-size, product-agnostic application identity. It keeps glyph geometry sta
|
|
|
584
639
|
sidebars, entity results, and recent-item rows while identity colour and material remain explicit
|
|
585
640
|
options rather than application globals.
|
|
586
641
|
|
|
587
|
-
- **Options** — `icon` (bundled name, Node, or lazy renderer), `color
|
|
642
|
+
- **Options** — `icon` (bundled name, Node, or lazy renderer), `color` (any CSS colour) or `tone`
|
|
643
|
+
(one of the nine categorical tones, drawn with its `--zx-color-tone-<tone>-solid` fill; `color`
|
|
644
|
+
wins when both are given), `size: 36`,
|
|
588
645
|
`iconSize: '52%'`, `label`, `badge`, `selected: false`, `shape: 'tile'|'circle'`,
|
|
589
646
|
`glass: false|'subtle'|'strong'`, and
|
|
590
647
|
additional `class` names.
|
|
@@ -697,6 +754,11 @@ Explicit duplicate file IDs are rejected; source order is preserved.
|
|
|
697
754
|
- **Events** — cancelable `activate {id,index,file,href,event}` and
|
|
698
755
|
`action {id,fileId,index,action,file,event}`, plus `datachange {items}` after a collection
|
|
699
756
|
mutation. Returned items are defensive snapshots.
|
|
757
|
+
- **Updates in place** — mutations are reconciled by file `id`: unchanged rows are left alone and a
|
|
758
|
+
changed one is updated through `FileItem.set()`, so upload progress can tick through
|
|
759
|
+
`updateItem(id, {progress})` while focus (say, on that file's Cancel action) and the scroll
|
|
760
|
+
position stay put. Removing the focused file moves focus to the file that takes its place. Files
|
|
761
|
+
without an `id` are rebuilt on every mutation.
|
|
700
762
|
<!-- /doc -->
|
|
701
763
|
|
|
702
764
|
<!-- doc:activity-item -->
|
|
@@ -738,6 +800,9 @@ whether a feed is newest- or oldest-first.
|
|
|
738
800
|
`updateItem(id,values,{silent?})`, `removeItem(id,{silent?})`, `setLoading()`, and `destroy()`.
|
|
739
801
|
- **Events** — `datachange {items}` after mutation. Each child's cancelable `zx-action` bubbles
|
|
740
802
|
naturally through the list for delegated application handling.
|
|
803
|
+
- **Focus** — entries are regrouped on every mutation; focus on an entry's control returns to the
|
|
804
|
+
same control of the same activity (matched by `id`, else position), so a feed prepending live
|
|
805
|
+
entries does not drop the reader to `<body>`.
|
|
741
806
|
- **Grouping** — groups keep first-seen group order and item source order. The exported pure
|
|
742
807
|
`groupActivityItems()` and `normalizeActivityItems()` helpers do not mutate caller arrays.
|
|
743
808
|
<!-- /doc -->
|
|
@@ -751,7 +816,9 @@ out, or mutates the session.
|
|
|
751
816
|
|
|
752
817
|
- **Options** — `account: {name, secondary, src?, initials?, status?, statusLabel?}`, `items`,
|
|
753
818
|
`compact: false`, `label`, and any top/bottom/left/right `placement`.
|
|
754
|
-
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setAccount()`, `setItems()`,
|
|
819
|
+
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setAccount()`, `setItems()`,
|
|
820
|
+
`setCompact(compact)` (the avatar alone, or with the name and secondary line — for a navigation
|
|
821
|
+
column that narrows and widens), `focus()`,
|
|
755
822
|
`getPanel()`, `destroy()`.
|
|
756
823
|
- **Events** — cancelable `select {value, item}`, `open`, and `close`. A canceled selection does
|
|
757
824
|
not run the item's callback and leaves the menu open, so an application can reject or confirm an
|
|
@@ -763,6 +830,47 @@ out, or mutates the session.
|
|
|
763
830
|
would give it, whatever order the stylesheets load in.
|
|
764
831
|
<!-- /doc -->
|
|
765
832
|
|
|
833
|
+
<!-- doc:workspace-switcher -->
|
|
834
|
+
### WorkspaceSwitcher
|
|
835
|
+
|
|
836
|
+
Composes `AppIcon` and `MenuButton` into the workspace switcher at the head of a navigation column:
|
|
837
|
+
a trigger with the current workspace's tile, name and secondary line, opening an APG menu of the
|
|
838
|
+
workspaces with the current one checked, then the application's own actions. Zx never decides
|
|
839
|
+
which workspaces a user may see, loads another workspace, or changes the session — switching is a
|
|
840
|
+
cancelable event the application acts on.
|
|
841
|
+
|
|
842
|
+
- **Options** — `workspaces: [{id, label, secondary?, icon?, tone?, color?, src?, initials?, badge?,
|
|
843
|
+
disabled?}]` (the tile is the image, else the icon, else the initial, on the tone — derived from
|
|
844
|
+
the label when neither `tone` nor `color` is given), `active` (defaults to the first enabled
|
|
845
|
+
workspace, which also replaces an id that is not listed or is disabled), `items` (actions after
|
|
846
|
+
the workspaces; the separator between them is added for you), `compact: false`, `heading` (null
|
|
847
|
+
resolves message `workspaceSwitcher.heading`, "Workspaces"; `''` leaves it out), `label` (`%1` is
|
|
848
|
+
the active workspace's name, filled in again after every switch; null resolves
|
|
849
|
+
`workspaceSwitcher.label`, "Switch workspace: %1", or `workspaceSwitcher.labelEmpty`, "Switch
|
|
850
|
+
workspace", while no workspace is active), and any `placement` (default `bottom-start`).
|
|
851
|
+
- **Messages** — `workspaceSwitcher.heading`, `workspaceSwitcher.label` (`%1` the workspace name),
|
|
852
|
+
`workspaceSwitcher.labelEmpty`; override them per instance through `msg`, or for every instance
|
|
853
|
+
through `setTranslator()`.
|
|
854
|
+
- **Methods** — `getActive()`, `setActive(id, {silent = true})` (`silent: false` emits `select`
|
|
855
|
+
first), `setWorkspaces(list)` (keeps the active workspace while it is still listed and enabled,
|
|
856
|
+
else the first enabled one becomes active), `setItems()`,
|
|
857
|
+
`setCompact(compact)`, `open()`, `close()`, `toggle()`, `isOpen()`, `focus()`, `getPanel()`,
|
|
858
|
+
`destroy()`.
|
|
859
|
+
- **Events** — cancelable `select {workspace, id, previous}`: a canceled switch keeps the previous
|
|
860
|
+
workspace active and the menu open. Cancelable `action {value, item}` for the extra items: a
|
|
861
|
+
canceled action does not run the item's callback. Both honour `preventDefault()` on the component
|
|
862
|
+
event and on the bubbling `zx-select` / `zx-action` DOM event alike. `open` and `close`. Choosing the current
|
|
863
|
+
workspace again only closes the menu.
|
|
864
|
+
- **Semantics** — one native `<button aria-haspopup="menu" aria-expanded>` trigger, named "Switch
|
|
865
|
+
workspace: <name>" (or the custom `label`) in both states; compact mode shows the tile alone and adds the name as its
|
|
866
|
+
`title`. Workspaces are `menuitemradio` rows with `aria-checked` under a presentational heading;
|
|
867
|
+
MenuButton keeps focus return, typeahead and the arrow-key map.
|
|
868
|
+
- **Appearance** — the trigger is a flat full-width row (tile, name, secondary line, chevron) with
|
|
869
|
+
the hover fill, at least a control and a space-2 tall; its tile is the module bar's tile size, so
|
|
870
|
+
as an `AppSidebar` `railHeader` it lines up with the module tiles under it. Keep it in step with a
|
|
871
|
+
modules sidebar by calling `setCompact(presentation !== 'expanded')` on `presentationchange`.
|
|
872
|
+
<!-- /doc -->
|
|
873
|
+
|
|
766
874
|
<!-- doc:select -->
|
|
767
875
|
### Select — APG editable combobox
|
|
768
876
|
|
|
@@ -775,8 +883,9 @@ Unifies single-select, local filtering, and async loading.
|
|
|
775
883
|
`clearable: false`, `filter: false | 'local' | async (query)=>items`, `searchKeys`,
|
|
776
884
|
`minQuery: 0`, `debounce: 200`, `listHeight: 280`, `groupKey`.
|
|
777
885
|
- **Getters** — `.value`, `.selected`.
|
|
778
|
-
- **Methods** — `
|
|
779
|
-
`open()`/`close()`, `enable()`/`disable()`,
|
|
886
|
+
- **Methods** — `get()` (the same as `.value`), `set(id, {silent})`, `setItems()`,
|
|
887
|
+
`setFixedItems()`, `setActions()`, `reset()`, `open()`/`close()`, `enable()`/`disable()`,
|
|
888
|
+
`focus()`.
|
|
780
889
|
- **Events** — `change {value, item}` (`item` is null on clear), preventable
|
|
781
890
|
`action {id, action, query, select}`, `open`, `close`, `query {query}`, `loaded {items}`.
|
|
782
891
|
- **Fixed choices** — `fixedItems` pins entries to the top of the list, separated by a rule (the
|
|
@@ -1072,7 +1181,10 @@ messages rather than the page, and uses the translucent glass surface tokens wit
|
|
|
1072
1181
|
when its last toast closes. One whose host was emptied or left the document is replaced on the
|
|
1073
1182
|
next toast rather than reused, and a scope that is itself detached falls back to the focused
|
|
1074
1183
|
element's scope. `Message.disposeRegion(scope)` → boolean removes a scope's region at once,
|
|
1075
|
-
toasts and all, for a host torn down while toasts are still showing.
|
|
1184
|
+
toasts and all, for a host torn down while toasts are still showing. While a modal Dialog or
|
|
1185
|
+
Sheet is open the region moves into the topmost one, so toasts and their actions stay visible
|
|
1186
|
+
and clickable above it, and moves back when it closes. A press on a toast dismisses no open menu
|
|
1187
|
+
or drawer.
|
|
1076
1188
|
- **Per-message options** — `kind: 'info'`, `timeout`, `closable: true`, `maxVisible: 5` (extras
|
|
1077
1189
|
queue, and hovering pauses dismissal).
|
|
1078
1190
|
- **Action** — `action: { label, onclick?, kind? }` renders one small button (`.zx-btn`,
|
|
@@ -1154,7 +1266,16 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
|
|
|
1154
1266
|
the browser through `showModal()`. Floating `'trap-focus'` and `false` sheets use a manual
|
|
1155
1267
|
Popover top layer, so they stay viewport-anchored even inside a transformed or clipped theme
|
|
1156
1268
|
scope while the page keeps scrolling and stays interactive. Escape and outside-click are
|
|
1157
|
-
re-implemented, and `'trap-focus'` adds `focusTrap()` on top
|
|
1269
|
+
re-implemented through the overlay stack, and `'trap-focus'` adds `focusTrap()` on top: Esc
|
|
1270
|
+
closes the sheet when focus is in it (or in a popup it opened) and no popup above it takes the
|
|
1271
|
+
key first, an Esc already handled inside (`defaultPrevented`) is ignored, and a press inside a
|
|
1272
|
+
menu or dropdown anchored in the sheet counts as inside the sheet. Closing the sheet closes the
|
|
1273
|
+
popups it opened. Light dismiss stops at the first layer that contains the press: a press inside
|
|
1274
|
+
the upper of two light-dismiss sheets (or inside any overlay above this one) closes neither the
|
|
1275
|
+
lower sheet nor anything beneath it; a press outside every layer closes each one that
|
|
1276
|
+
light-dismisses. A floating non-modal sheet opened from inside a modal Dialog is hosted by that
|
|
1277
|
+
dialog while open, so it stays usable above it, closes when the dialog closes, and goes back to
|
|
1278
|
+
its theme scope when it closes. Their top-layer backdrop is
|
|
1158
1279
|
explicitly transparent and non-filtering; `backdrop` is meaningful only when `modal` is `true`.
|
|
1159
1280
|
A docked Sheet remains a native non-modal dialog in its Dock's flow.
|
|
1160
1281
|
- **Resizing and detents** — `resizable` makes the inner edge draggable between `min` and `max`.
|
|
@@ -1240,7 +1361,25 @@ DOM child of the anchor's nearest Zx theme scope while the browser lifts it into
|
|
|
1240
1361
|
- **Hosting** — the host is resolved again on every `open()`. A dropdown created before its anchor
|
|
1241
1362
|
is in the document keeps its panel detached until the first open instead of parking it on
|
|
1242
1363
|
`<body>`, so an anchor inserted into a `[data-zx-theme]` region or a `.zx-scope` afterwards still
|
|
1243
|
-
gets its theme. Tooltip, MenuButton, and ContextMenu panels follow the same rule.
|
|
1364
|
+
gets its theme. Tooltip, MenuButton, and ContextMenu panels follow the same rule. While open, a
|
|
1365
|
+
panel whose anchor sits in an open `<dialog>` (a modal Dialog or Sheet, a focus-trapping or
|
|
1366
|
+
non-modal Sheet) is hosted by that dialog instead, so `showModal()` does not make it inert and a
|
|
1367
|
+
focus trap still reaches it; it returns to the theme scope when it closes.
|
|
1368
|
+
- **Overlay stack** — an open panel is one entry on the shared overlay stack
|
|
1369
|
+
(`src/core/overlay-stack.js`). Esc closes only the topmost layer that takes it, and an Esc that
|
|
1370
|
+
something inside already handled (`defaultPrevented`) closes nothing. A press outside the panel
|
|
1371
|
+
and every overlay opened from it closes it; a press on the anchor does not. Light dismiss stops
|
|
1372
|
+
at the first layer that contains the press, so a press inside an overlay above the panel (one
|
|
1373
|
+
not opened from it) leaves the panel open; nothing below an open modal reacts at all. A panel
|
|
1374
|
+
opened from inside another overlay closes with that overlay.
|
|
1375
|
+
- **Keyboard** — the panel follows its trigger in the Tab order: Tab on the anchor of an open panel
|
|
1376
|
+
moves to the panel's first Tab stop, or onto the panel itself when it has a `tabindex`; Tab past
|
|
1377
|
+
the last stop continues with whatever follows the anchor; Shift+Tab from the first
|
|
1378
|
+
stop returns to the anchor. A Tab stop is what the browser itself stops at: a `tabindex="-1"` part
|
|
1379
|
+
of a composite (a Select's toggle), a roving toolbar's inactive items, disabled, hidden and inert
|
|
1380
|
+
controls, and all but one radio of a group are not. A panel with no Tab stop is skipped. Esc and `close()` with
|
|
1381
|
+
focus inside return focus to the anchor. Opening moves no focus — give the panel `role` and
|
|
1382
|
+
`aria-*` semantics yourself if it is more than a disclosure.
|
|
1244
1383
|
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setContent()`, `getPanel()`.
|
|
1245
1384
|
- **Events** — `open`, `close`.
|
|
1246
1385
|
<!-- /doc -->
|
|
@@ -1260,8 +1399,16 @@ A button that opens a `role="menu"`.
|
|
|
1260
1399
|
default.
|
|
1261
1400
|
- `role: 'menuitemcheckbox'|'menuitemradio'` with `checked` exposes an APG checked action. The
|
|
1262
1401
|
application owns state changes and calls `setItems()` with the new model.
|
|
1263
|
-
-
|
|
1264
|
-
|
|
1402
|
+
- `{type: 'choice', label, value, options: [{value, label, icon|glyph}], onselect(value, entry,
|
|
1403
|
+
menu)}` draws a setting chosen in place — a theme, a layout: the label, then a small segmented
|
|
1404
|
+
group of `menuitemradio` options named by it. Choosing an option checks it and calls
|
|
1405
|
+
`onselect`; the menu stays open so the change shows, and `select` does not fire. Left and Right
|
|
1406
|
+
move within the row; Up and Down leave it, landing on another row's checked option.
|
|
1407
|
+
`AccountMenu` takes the same entries; `ContextMenu`, which closes on every activation, leaves
|
|
1408
|
+
them out.
|
|
1409
|
+
- **Methods** — `setItems()`, `open({returnFocus})`, `close()`, `toggle()` (opens when closed,
|
|
1410
|
+
closes when open, moves no focus), `isOpen()`, `setLabel()`, `getTrigger()`, `getPanel()`,
|
|
1411
|
+
`focusFirst({returnFocus})`, `focusLast({returnFocus})`.
|
|
1265
1412
|
- **Events** — `select {value, item}`, `open`, `close`.
|
|
1266
1413
|
- **Focus return** — choosing an item closes the menu and returns focus to the trigger before the
|
|
1267
1414
|
`select` event and the item's `onselect` run, so a dialog they open returns to the trigger when
|
|
@@ -1273,7 +1420,9 @@ A button that opens a `role="menu"`.
|
|
|
1273
1420
|
leaves the menu open with focus back on the item, which is the composition seam used by
|
|
1274
1421
|
`AccountMenu` and rail flyouts.
|
|
1275
1422
|
- **Keyboard** — ArrowDown/Enter/Space open and focus the first item; ArrowUp opens and focuses the
|
|
1276
|
-
last; arrows cycle; Home/End; typeahead; Enter/Space select; Esc/Tab close.
|
|
1423
|
+
last; arrows cycle; Home/End; typeahead; Enter/Space select; Esc/Tab close. Esc reaches the menu
|
|
1424
|
+
through the overlay stack, so inside a Sheet or Dialog it closes the menu and leaves the sheet
|
|
1425
|
+
open; a menu opened inside a modal Dialog is hosted by it and stays usable.
|
|
1277
1426
|
- **Appearance** — the highlighted item is a neutral step, a checked item keeps its accent check
|
|
1278
1427
|
mark, and section headings are sentence-case captions. ContextMenu shares the same rules.
|
|
1279
1428
|
<!-- /doc -->
|
|
@@ -1321,7 +1470,7 @@ A description bubble anchored to another element. The panel is a manual popover
|
|
|
1321
1470
|
at all and leaves everything to the methods), `disabled` (false).
|
|
1322
1471
|
- **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.
|
|
1323
1472
|
- **Events** — `open`, `close`. Both fire only on a real transition, so a `show()` on an open tooltip is silent.
|
|
1324
|
-
- **Keyboard** — Focusing the anchor opens the tooltip at once; moving focus away closes it. `Escape` closes it without moving focus
|
|
1473
|
+
- **Keyboard** — Focusing the anchor opens the tooltip at once; moving focus away closes it. `Escape` closes it without moving focus (WCAG 1.4.13 "dismissible") — and only it, when it is the topmost open overlay, so one press never closes a tooltip and the menu beneath it. A `pointerdown` anywhere closes it, and focus that arrives with a click does not re-open it. A closed tooltip listens to nothing outside its anchor; an open one is an entry on the shared overlay stack.
|
|
1325
1474
|
<!-- /doc -->
|
|
1326
1475
|
|
|
1327
1476
|
<!-- doc:split-view -->
|
|
@@ -1356,9 +1505,9 @@ range select.
|
|
|
1356
1505
|
render?: (row,i)=>Node|string, sortValue?, headerTitle? }]`, `data`, `rowId: 'ID'`,
|
|
1357
1506
|
`sort: { id, dir }`, `sortMode: 'local'|'server'`, `selectable: false|'single'|'multi'`,
|
|
1358
1507
|
`stickyHeader: true`, `height`, `emptyText`, `rowClass`, `zebra: true`, `variant:
|
|
1359
|
-
'default'|'plain'`,
|
|
1360
|
-
unit, and decimals may be row callbacks; values
|
|
1361
|
-
formatting is locale-aware.
|
|
1508
|
+
'default'|'plain'`, `hierarchy: false|{parentId, column?, expanded?}`, `interactive: false`,
|
|
1509
|
+
and `label`/`labelledBy`. Locale, currency, unit, and decimals may be row callbacks; values
|
|
1510
|
+
remain numbers in row data while display formatting is locale-aware.
|
|
1362
1511
|
- **Appearance** — the header sits on the table's surface (no band) in the muted text colour at a
|
|
1363
1512
|
medium weight, sentence case; the sorted column's label takes the text colour beside its arrow.
|
|
1364
1513
|
Rows are divided by `--zx-color-border-subtle`, the header by the ordinary border, and every cell
|
|
@@ -1379,6 +1528,44 @@ range select.
|
|
|
1379
1528
|
top bar, sets `aria-busy`, and is cleared by the next `setData`).
|
|
1380
1529
|
- **Events** — `rowclick {row, id, index, event}`, `rowdblclick`, `sort {id, dir}`,
|
|
1381
1530
|
`selectionchange {rows, ids}`, `datachange {rows}`, and `rowtoggle {row, id, expanded}`.
|
|
1531
|
+
`rowclick` fires for Enter on a focused row as well as for a click; `event` is then the keydown.
|
|
1532
|
+
- **Keyboard** — an *interactive* table implements the WAI-ARIA APG **Grid** pattern
|
|
1533
|
+
(`role="grid"`, with `aria-multiselectable` for `selectable: 'multi'`), or the **Treegrid**
|
|
1534
|
+
pattern with `hierarchy`. A table is interactive when it is `selectable`, has `editMode` or
|
|
1535
|
+
`hierarchy`, sets `interactive: true`, or has a `rowclick`/`rowdblclick` listener attached through
|
|
1536
|
+
`on()` or an `onrowclick`/`onrowdblclick` option (attaching one later turns the grid on). A host
|
|
1537
|
+
that listens only for the bubbling `zx-rowclick` DOM event passes `interactive: true`. TableView
|
|
1538
|
+
always bridges `rowclick` to `recordclick`, so its table is always a grid. Any other table keeps
|
|
1539
|
+
plain `<table>` semantics, no grid role, no name unless `label` is given, and no row Tab stop.
|
|
1540
|
+
In a grid the rows are one Tab stop with a roving `tabindex`; the row checkboxes and disclosure
|
|
1541
|
+
buttons leave the Tab order because the row keys cover them (links and buttons a host renders
|
|
1542
|
+
in cells keep theirs). Focus is on rows by default and on cells when `editMode` is set —
|
|
1543
|
+
reflected as `data-focus="row"|"cell"` on the root. ArrowUp/ArrowDown move a row,
|
|
1544
|
+
ArrowLeft/ArrowRight a cell (cell focus), Home/End go to the first/last row (cell focus:
|
|
1545
|
+
first/last cell of the row) and Ctrl+Home/End to the first/last row's corner cell,
|
|
1546
|
+
PageUp/PageDown move by the rows visible in the scroll region (10 when the table does not scroll
|
|
1547
|
+
itself). Enter activates exactly like a click (`rowclick`; selects in `single`), Enter/F2 on an
|
|
1548
|
+
editable cell opens its editor, Space toggles the row's selection when `selectable` (activates
|
|
1549
|
+
otherwise), Shift+ArrowUp/Down extends a `multi` selection from its anchor, and Ctrl/Cmd+A
|
|
1550
|
+
selects the rows in the DOM — like the header checkbox, not the rows `growing` holds back nor the
|
|
1551
|
+
children of collapsed branches. A sort, new data, or a change in expansion starts the anchor
|
|
1552
|
+
over. Treegrid: ArrowRight expands a collapsed row, then moves into its first child; ArrowLeft
|
|
1553
|
+
collapses an expanded row, then moves to its parent. With cell focus, ArrowLeft from the first
|
|
1554
|
+
cell lands on the row and ArrowRight from the row goes back to its first cell. The focused row
|
|
1555
|
+
(and column) is kept by id across `setData`, sorting, expansion, growing, and `updateRow`; when
|
|
1556
|
+
a collapsed ancestor hides it, the ancestor takes focus, and when it is gone, its neighbour does.
|
|
1557
|
+
Focus is only restored when the body had it, and without scrolling, so a polling refresh never
|
|
1558
|
+
pulls the page back to the table. An emptied table keeps focus on its frame until rows return.
|
|
1559
|
+
Column headers keep their sort buttons in the Tab order rather than joining arrow navigation —
|
|
1560
|
+
the one deliberate departure from the APG grid, which puts the header row inside it. Rows and
|
|
1561
|
+
cells show an inset `--zx-focus-ring` with a transparent outline that forced colours paint.
|
|
1562
|
+
- **Naming** — `label` or `labelledBy` (an element id) names the table; a grid without either is
|
|
1563
|
+
named by the `table.label` message (English "Table"), so pass a real name. Row controls are named
|
|
1564
|
+
after the row's first cell with text through `table.selectRow` (`Select %1`), `table.expandRow`,
|
|
1565
|
+
`table.collapseRow`, and `table.moveRow`; `table.row` (`row %1`) stands in for a row without
|
|
1566
|
+
text, and `table.selectAll` names the header checkbox. Sort buttons and selection checkboxes are
|
|
1567
|
+
at least 24×24 px targets; the checkbox is drawn so the glyph stays `--zx-space-4` while a
|
|
1568
|
+
transparent border makes up the target.
|
|
1382
1569
|
- **Hierarchy** — flat parent references are projected as an ARIA `treegrid`; `getData()` and all
|
|
1383
1570
|
editing/mutation APIs stay flat and ID-based. Orphans become roots, cycles render once, local
|
|
1384
1571
|
sort reorders siblings without mixing levels, and growing counts visible rows. Use
|
|
@@ -1470,7 +1657,16 @@ range select.
|
|
|
1470
1657
|
`{label, target}` object names the chooser's trigger and mounts the disclosure into a host
|
|
1471
1658
|
element — a list toolbar, say — instead of the view's own. `label` omitted resolves
|
|
1472
1659
|
`recordView.fields`. This is what lets a translated application use the chooser at all: the
|
|
1473
|
-
trigger used to be the literal `Fields`, with no way to change it or move it.
|
|
1660
|
+
trigger used to be the literal `Fields`, with no way to change it or move it. `TableView`,
|
|
1661
|
+
`CardView`, and `KanbanView` all honour `target`.
|
|
1662
|
+
|
|
1663
|
+
**The chooser is public.** `getFieldControls()` returns the view's field chooser — the shared
|
|
1664
|
+
disclosure, or for a `TableView` with `fieldControls: true` its Table's column chooser — and
|
|
1665
|
+
`mountFieldControls(target)` moves it into a host element (`null` hands it back). The view keeps
|
|
1666
|
+
owning it wherever it sits and removes it on `destroy()`; `ListToolbar.bind()` uses exactly this.
|
|
1667
|
+
In the shared chooser the move buttons keep focus on the field they moved (or its other button once
|
|
1668
|
+
one end disables), a polite live region announces "Name moved to position 2 of 5"
|
|
1669
|
+
(`recordView.fieldMoved`), and Escape closes the disclosure and returns focus to its trigger.
|
|
1474
1670
|
|
|
1475
1671
|
`TableView` is the record-oriented entry point above the lower-level `Table`. It extends
|
|
1476
1672
|
`RecordView` and composes one complete `Table`, so editing, hierarchy, growing, row movement,
|
|
@@ -1484,7 +1680,8 @@ responsive stacking, loading, and advanced table events remain available through
|
|
|
1484
1680
|
`fieldControls: true`, and `emptyText`. At least one field remains visible.
|
|
1485
1681
|
- **Shared methods** — `setData()`, `addData()`, `updateRecord()`, `removeRecord()`, `getRecord()`,
|
|
1486
1682
|
`getData()`, field getters/order/visibility methods, `setSort()`/`getSort()`, selection methods,
|
|
1487
|
-
`setLoading()`, `getViewState()`,
|
|
1683
|
+
`setLoading()`, `getViewState()`, `setViewState()`, `getFieldControls()`, and
|
|
1684
|
+
`mountFieldControls(target)`.
|
|
1488
1685
|
- **Portable state** — `{version:1,fieldOrder,hiddenFields,sort}` is JSON-safe configuration only.
|
|
1489
1686
|
Records, selection, DOM nodes, rendered values, and callbacks are never serialized. A state from
|
|
1490
1687
|
one record view may seed another; unknown fields are ignored and new fields append.
|
|
@@ -1503,7 +1700,15 @@ portable state as TableView and KanbanView.
|
|
|
1503
1700
|
|
|
1504
1701
|
- **Options** — the shared RecordView options plus `titleField`, `subtitleField`, `preview`,
|
|
1505
1702
|
`previewAlt`, `link`, `actions`, `groupBy`, `groupOrder`, `minCardWidth`, `maxColumns`,
|
|
1506
|
-
`variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, and `label
|
|
1703
|
+
`variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, and `label` (null
|
|
1704
|
+
resolves message `cardView.label`, "Card view").
|
|
1705
|
+
- **Messages** — `cardView.label`, `cardView.sort`, `cardView.sortCards`, `cardView.unsorted`,
|
|
1706
|
+
`cardView.sortAscending`/`cardView.sortDescending` (`%1` the field), `cardView.ungrouped`
|
|
1707
|
+
("Ungrouped"), `cardView.groupEmpty` ("No records in this group."), `table.empty` for the empty
|
|
1708
|
+
collection ("No records", shared with Table), and the card's own `recordCard.select`,
|
|
1709
|
+
`recordCard.actions` (`%1` the title), `recordCard.untitled`, `recordCard.selected`,
|
|
1710
|
+
`recordCard.notSelected`. English comes from the default catalogue; override per instance
|
|
1711
|
+
through `msg`, or for every instance through `setTranslator()`.
|
|
1507
1712
|
- **Selection trigger** — `selectionTrigger:'checkbox'` preserves existing selection behavior.
|
|
1508
1713
|
With `'card'`, selectable backgrounds toggle on click, Enter or Space, Shift+click selects a range,
|
|
1509
1714
|
and the checkbox is omitted. Links, actions and text selection keep their native behavior. Selection
|
|
@@ -1636,7 +1841,18 @@ windowed loader.
|
|
|
1636
1841
|
`locale`, `weekStart: 1`, `weekNumbers: true`, `workweek: false`, `agendaDays: 14`,
|
|
1637
1842
|
`slotDuration: 30`, `slotMinTime: 0`, `slotMaxTime: 1440`, `scrollTime: 480`, `eventLimit: 3`,
|
|
1638
1843
|
`selectable: false`, `editable: false`, `eventStartEditable`, `eventDurationEditable`,
|
|
1639
|
-
`optimistic: true`, `nowIndicator: true`, `now`, `disabled`, and `renderEvent(event)`.
|
|
1844
|
+
`optimistic: true`, `nowIndicator: true`, `now`, `disabled`, `msg`, and `renderEvent(event)`.
|
|
1845
|
+
- **Messages** — every key is namespaced `calendar.*`: `calendar.today`, `calendar.previous`,
|
|
1846
|
+
`calendar.next`, `calendar.new`, `calendar.agenda`/`day`/`week`/`month`/`year` (the view
|
|
1847
|
+
switch), `calendar.viewSwitch`, `calendar.label` (the root's name), `calendar.loading`,
|
|
1848
|
+
`calendar.noEvents`, `calendar.allDay`, `calendar.more` and `calendar.weekNumber` (`%1`),
|
|
1849
|
+
`calendar.dayEvent`/`calendar.dayEvents`, `calendar.resize`, `calendar.moveStarted`,
|
|
1850
|
+
`calendar.resizeStarted`, `calendar.editPreview` (`%1` title, `%2`–`%3` the times),
|
|
1851
|
+
`calendar.editCancelled`, `calendar.editCommitted`. Until 6.0 the bare keys of earlier versions
|
|
1852
|
+
(`today`, `next`, `loading`, …, and `calendar` for `calendar.label`) are still honoured, read
|
|
1853
|
+
right after the namespaced key in the `msg` option and in the host translator; rename them.
|
|
1854
|
+
The built-in English now lives in the default catalogue rather than in `Calendar.defaults.msg`,
|
|
1855
|
+
so a host translator reaches every calendar string.
|
|
1640
1856
|
- **Event input** — generic records default to `{id,title,start,end,allDay,color,location,editable,
|
|
1641
1857
|
durationEditable}`. Every field has a string-or-function reader (`eventId`, `eventTitle`,
|
|
1642
1858
|
`eventStart`, …); numeric dates use `dateUnit: 'milliseconds'|'seconds'`. Inputs are normalized
|
|
@@ -1683,6 +1899,8 @@ Declarative client-side filter bar producing a filtered array, commonly wired to
|
|
|
1683
1899
|
|
|
1684
1900
|
- **Options** — `filters: [{ type: 'select'|'text'|'custom', id, label, field(s)|get, options?,
|
|
1685
1901
|
predicate?, emptyLabel?, placeholder? }]`, `data`, `autoApply: true`, `clearLabel`.
|
|
1902
|
+
- **Messages** — `dataFilter.clear` ("Clear", unless `clearLabel` is given) and `dataFilter.all`
|
|
1903
|
+
("All", a select filter's empty option unless it sets `emptyLabel`).
|
|
1686
1904
|
- **Methods** — `setData()`, `apply()` → rows, `clear()`, `getState()`, `setState()`,
|
|
1687
1905
|
`addFilter()`.
|
|
1688
1906
|
- **Events** — `filter {rows, state}`.
|
|
@@ -1700,7 +1918,7 @@ form renders, with no per-application translation layer in between.
|
|
|
1700
1918
|
- **Options** — `fields: Record<string, {type, label?, options?, min?, max?, step?, search?}>`,
|
|
1701
1919
|
`order` (display order; omitted uses object order), `value` (the applied filters, drafted
|
|
1702
1920
|
from), `mode: 'draft'|'live'`, `serialize: 'seconds'|'ms'|'iso'|(date, edge) => unknown`,
|
|
1703
|
-
`emptyText`, `msg`.
|
|
1921
|
+
`timeZone` (IANA zone the days are counted in; null = the runtime's own), `emptyText`, `msg`.
|
|
1704
1922
|
- **Types** — `text`, `select`, `date`, `date:range`, `int`, `float`, `number`, and
|
|
1705
1923
|
`int:range` / `float:range` / `number:range`. An unrecognised type renders a visible,
|
|
1706
1924
|
labelled placeholder naming it and is excluded from the value — never dropped silently.
|
|
@@ -1725,7 +1943,10 @@ Five behaviours are deliberate rather than incidental:
|
|
|
1725
1943
|
|
|
1726
1944
|
`serialize` decides how a date leaves the component, because the answer is a wire-format
|
|
1727
1945
|
decision rather than an application one. A range's upper bound takes the **end** of its day, or
|
|
1728
|
-
a one-day range matches nothing.
|
|
1946
|
+
a one-day range matches nothing. `timeZone` decides *which* day: with the ZeyOS instance's zone
|
|
1947
|
+
(`'Europe/Vienna'`), 15 January serializes from 23:00 UTC on the 14th wherever the browser is, and
|
|
1948
|
+
an incoming Unix timestamp shows as the day it falls on there — the rule `zeyosForm()` and
|
|
1949
|
+
`zeyosTable()` apply to date-only fields. An unknown zone throws a `RangeError`.
|
|
1729
1950
|
<!-- /doc -->
|
|
1730
1951
|
|
|
1731
1952
|
<!-- doc:filter -->
|
|
@@ -1742,6 +1963,11 @@ adapter to compile into its own backend query language.
|
|
|
1742
1963
|
debounce?}`. Built-in types cover text, number/money, date/datetime, boolean, enum/status/
|
|
1743
1964
|
priority/country/currency/unit/entity, and tags. Compatible operators and their arity come from
|
|
1744
1965
|
the exported `filterOperators` matrix; applications may explicitly override/add operator IDs.
|
|
1966
|
+
- **Relative dates** — `withinLast` and `withinNext` take a span, not two dates: the editor is a
|
|
1967
|
+
whole-number amount and a unit select, and the value is the pair `[amount, unit]` with `unit` one
|
|
1968
|
+
of `RELATIVE_DATE_UNITS` (`'days'|'weeks'|'months'`), e.g. `[7, 'days']`. `validate()` requires a
|
|
1969
|
+
positive whole amount and a known unit; `relativeDateValueError(value)` is the same check for an
|
|
1970
|
+
adapter. A value that is not such a pair (two dates) shows as an empty amount.
|
|
1745
1971
|
- **Options** — `fields`, `value`, `operators`, `allowGroups: true`, `maxDepth: 3`,
|
|
1746
1972
|
`maxConditions: 50`, `autoApply: false`, `readonly`, `disabled`, `applyLabel`, `clearLabel`,
|
|
1747
1973
|
`searchable: false`, `layout: 'standard'|'compact'`, `rootLogic: null|'and'|'or'`,
|
|
@@ -1844,24 +2070,45 @@ breadcrumb.
|
|
|
1844
2070
|
mounted twice repeats no id and a field `stage` never collides with a `#stage` elsewhere; the
|
|
1845
2071
|
label, description, and message ids derive from it (`<id>-label`). Radio groups are named after
|
|
1846
2072
|
the control id. An `id` in the field options that differs from the key is used as given.
|
|
1847
|
-
- **Methods** — `addField(id, opts)`, `getField`/`hasField`/`getFields`,
|
|
2073
|
+
- **Methods** — `addField(id, opts|Field|element|component)`, `getField`/`hasField`/`getFields`,
|
|
1848
2074
|
`getValues`/`setValues`/`getValue`/`setValue`, `reset`, `clear`, `focus(id)`,
|
|
1849
2075
|
`setHighlights`/`clearHighlights`.
|
|
2076
|
+
- **Adopting a component** — `addField(id, component)` wraps any component (or element) that is
|
|
2077
|
+
not a registered field type and drives it through its own API. Values: `getValue`/`setValue`,
|
|
2078
|
+
`get`/`set`, `getValues`/`setValues`, `isChecked`/`setChecked`, or a `value` property, tried in
|
|
2079
|
+
that order (a Toggle reads its on-value, as the `toggle` type does); an element is read through
|
|
2080
|
+
`value`, or `checked` for a checkbox or radio. Disabled: `setDisabled(bool)`, then
|
|
2081
|
+
`enable()`/`disable()`, then a `disabled` property; a plain wrapper gets `aria-disabled` and its
|
|
2082
|
+
form controls disabled. Read-only: `setReadonly`/`setReadOnly` or the element's `readOnly`. The
|
|
2083
|
+
component's own `change` becomes the field's `change`, once per edit. Writes keep the caller's
|
|
2084
|
+
`silent`: `setValues(values)` lets the component emit its own `change` to its own listeners and
|
|
2085
|
+
the field emit one `change`; `setValues(values, {silent: true})` and `reset()` stay quiet.
|
|
2086
|
+
Removing the field puts the component back, unsubscribes from it, and undoes the disabled and
|
|
2087
|
+
read-only state the field set.
|
|
1850
2088
|
|
|
1851
2089
|
#### Field
|
|
1852
2090
|
|
|
1853
2091
|
- **Options** — `id` (the control's DOM id; generated when omitted), `name` (the field name and
|
|
1854
2092
|
native control `name`; defaults to `id` on a standalone field), `type: 'text'`, `label`,
|
|
1855
|
-
`description`, `value`, `placeholder`, `required`, `disabled`, `
|
|
1856
|
-
|
|
2093
|
+
`description`, `value`, `placeholder`, `required`, `disabled`, `readonly` (shown and focusable,
|
|
2094
|
+
not editable; ignored by types whose control has no read-only state, such as `checkbox`,
|
|
2095
|
+
`select`, `zxselect`, `date`, `time`), `options`, `layout: 'stack'|'inline'`, `props: {}`.
|
|
1857
2096
|
- **Properties** — `controlId` (the control's DOM id), `name`.
|
|
1858
2097
|
- **Built-in types** — `text`, `password`, `textarea`, `checkbox`, `int`, `float`, `select`,
|
|
1859
2098
|
`optionlist`, `hidden`, `html` (display content as text: no input frame, a control's height,
|
|
1860
2099
|
inset like the values around it in a quiet form).
|
|
1861
|
-
- **Methods** — `getValue`/`setValue`, `focus`, `reset`, `setDisabled`,
|
|
2100
|
+
- **Methods** — `getValue`/`setValue`, `focus`, `reset`, `setDisabled`, `setReadonly`,
|
|
1862
2101
|
`setHighlight(msg, kind)`, `clearHighlight`, `getInput`, `own(child)`, plus the statics
|
|
1863
2102
|
`Field.register(type, adapter)` and `Field.has(type)`.
|
|
1864
|
-
- **Events** — `change {value}`,
|
|
2103
|
+
- **Events** — `change {value}`, once per committed edit whatever the type (a typed value on
|
|
2104
|
+
leaving the input, a slider on release, `code` when the editor loses focus after an edit);
|
|
2105
|
+
`input {value}` for every keystroke or drag step from the types that tell the two apart
|
|
2106
|
+
(`code`, `slider`); `invalid {message}`.
|
|
2107
|
+
- **Adapters** — `Field.register(type, (field, options) => ({el, get, set, focus, setDisabled,
|
|
2108
|
+
setReadonly?, destroy?, labelTarget?}))`. An adapter whose widget reports its own commits
|
|
2109
|
+
forwards them with `field.emit('change', {value})` and stops the native `change` its inner
|
|
2110
|
+
control bubbles (`field.listen(widget.el, 'change', (e) => e.stopImmediatePropagation())`), so
|
|
2111
|
+
the Field does not report the same edit twice.
|
|
1865
2112
|
- **Labels** — small, medium weight, in the muted text colour: quieter than the values they name.
|
|
1866
2113
|
With `layout: 'inline'` the label lines up with the control's first line.
|
|
1867
2114
|
|
|
@@ -2350,7 +2597,7 @@ columns, in what shape. The parts have always existed (`Search`, `SortControl`,
|
|
|
2350
2597
|
forgetting the same thing.
|
|
2351
2598
|
|
|
2352
2599
|
- **Options** — `search: {placeholder?, value?, debounce?, clearable?}|false`, `count`,
|
|
2353
|
-
`countText`, `views: Array<string|{id, label?, icon?}>` (string presets `auto|cards|
|
|
2600
|
+
`countText`, `views: Array<string|{id, label?, icon?}>` (string presets `auto|table|list|cards|gallery|board`),
|
|
2354
2601
|
`view`, `tools: [{id, label?, icon?, badge?, badgeKind?, pressed?, disabled?, onclick?}]`,
|
|
2355
2602
|
`label`, `msg`.
|
|
2356
2603
|
- **Methods** — `setCount()`, `setBadge()`, `setPressed()`, `setToolDisabled()`, `setTools()`,
|
|
@@ -2370,18 +2617,90 @@ Three properties are the reason to use this rather than assemble your own:
|
|
|
2370
2617
|
The current view and a pressed tool take the neutral `--zx-color-bg-current` fill — a mode the
|
|
2371
2618
|
list is in, not a pick — never the accent tint.
|
|
2372
2619
|
|
|
2373
|
-
`bind(view)` is optional sugar for a `TableView` or `
|
|
2374
|
-
importing them, adopts the view's shape, and mounts
|
|
2375
|
-
|
|
2620
|
+
`bind(view)` is optional sugar for a `TableView`, `CardView`, or `KanbanView`: it feature-detects
|
|
2621
|
+
rather than importing them, adopts the view's shape, and mounts the view's field chooser into the
|
|
2622
|
+
tools slot through the view's public `mountFieldControls()` — for a default `TableView` that is its
|
|
2623
|
+
Table's column chooser. `unbind()` hands it back to the view, and a view destroyed first takes it
|
|
2624
|
+
with it. A chooser already mounted here with `fieldControls: {target: toolbar.getToolsElement()}`
|
|
2625
|
+
is left where it is. The toolbar is fully functional without `bind()`.
|
|
2626
|
+
<!-- /doc -->
|
|
2627
|
+
|
|
2628
|
+
<!-- doc:view-settings -->
|
|
2629
|
+
### ViewSettings — the content of a view-settings drawer
|
|
2630
|
+
|
|
2631
|
+
Which fields a record view shows and in what order, which hidden field to bring back, what the
|
|
2632
|
+
records are grouped by, and which fields a board uses for columns and swim lanes. It is content,
|
|
2633
|
+
not a container: put it in a `Sheet`, a `Dropdown`, or a settings page.
|
|
2634
|
+
|
|
2635
|
+
- **Options** — `fields: ViewSettingsField[]` (a `ViewField` plus `type`, `groupable`, `locked`),
|
|
2636
|
+
`fieldOrder`, `hiddenFields`, `groupBy`, `grouping: boolean|{fields?}`,
|
|
2637
|
+
`board: {columnBy, swimlaneBy, showEmptyColumns?, fields?}|false` (`fields` restricts the
|
|
2638
|
+
Columns and Swim lanes choices; default: the Group by fields), `sections` (order and presence of
|
|
2639
|
+
`'fields'|'grouping'|'board'`), `types: Record<type, {icon?, label?}>`, `searchThreshold` (8),
|
|
2640
|
+
`headingLevel` (3), `label`, `msg`.
|
|
2641
|
+
- **Methods** — `getState()`, `setState(partial, {silent})`, `setFields()`, `getFields()`,
|
|
2642
|
+
`showField()`, `hideField()`, `moveField(id, index)`, `bind(view, {board?})`, `unbind()`,
|
|
2643
|
+
`focus()`, `destroy()`.
|
|
2644
|
+
- **Events** — `change {state, reason: 'order'|'visibility'|'group'|'board'}`,
|
|
2645
|
+
`fieldorderchange {order}`, `fieldvisibilitychange {visible, hidden}`, `groupchange {field}`,
|
|
2646
|
+
`boardchange {board}`.
|
|
2647
|
+
- **State** — `{fieldOrder, hiddenFields, groupBy, board}`: JSON-safe, ready to persist.
|
|
2648
|
+
|
|
2649
|
+
The shown fields are listed in order, each with a drag handle, its type glyph, and a hide button;
|
|
2650
|
+
the hidden ones follow with an explicit **Add** button, which puts the field at the end of the
|
|
2651
|
+
shown list. Above `searchThreshold` fields a search narrows the hidden list (label, id, and type
|
|
2652
|
+
name, ignoring case and accents) and Enter adds the first match. A `locked` field — the record
|
|
2653
|
+
title — has neither handle nor hide button and keeps its place while the others move around it.
|
|
2654
|
+
`locked` governs only what the panel offers: a bound view has no such notion, so a locked field the
|
|
2655
|
+
view hides itself appears under Hidden with Add like any other. The last shown field cannot be
|
|
2656
|
+
hidden.
|
|
2657
|
+
|
|
2658
|
+
Rows reorder by pointer (mouse, touch, and pen share one path: drag the handle, an insertion line
|
|
2659
|
+
shows where it lands, Escape cancels; near the top or bottom edge of the nearest scroll container,
|
|
2660
|
+
the container scrolls) and by keyboard on the focused handle: ArrowUp/ArrowDown
|
|
2661
|
+
move one step, Home/End to the ends, Space or Enter picks the row up and drops it, Escape returns
|
|
2662
|
+
it. Every move is announced in a polite live region ("Owner moved to position 3 of 6"). A redraw
|
|
2663
|
+
from outside — `setState()`, `setFields()`, a bound view's change — cancels a pick-up or drag in
|
|
2664
|
+
progress (announced) and keeps focus on the same field's control.
|
|
2665
|
+
|
|
2666
|
+
The type glyph comes from a built-in map that `types` extends: text, number (int, float,
|
|
2667
|
+
percent), money, date, datetime, time, boolean, select (enum), status, priority, entity, tags,
|
|
2668
|
+
url, email, image, file, rating. The type name is the glyph's `title` and visually hidden text,
|
|
2669
|
+
so it is seen and heard. Grouping and the board offer fields with `groupable !== false` and a
|
|
2670
|
+
discrete type (text, select, status, priority, boolean, entity), or `groupable: true`;
|
|
2671
|
+
`grouping.fields` restricts the Group by choices.
|
|
2672
|
+
|
|
2673
|
+
`bind(view)` connects a `TableView`, `CardView`, or `KanbanView`: it reads the view's fields and
|
|
2674
|
+
state, pushes order and visibility through `setFieldOrder()`/`setFieldVisible()`, and follows the
|
|
2675
|
+
view's own `statechange`; `unbind()` releases the view. A panel given its own `fields` lists
|
|
2676
|
+
those and no others (the title a card draws is not a column to offer); given none, it lists every
|
|
2677
|
+
field the view has. The view's labels and types win over the panel's descriptors, which add only
|
|
2678
|
+
`locked`, `groupable`, and a type where the view has just the `'text'` default. While bound the view owns the field list: call the view's `setFields()` — the
|
|
2679
|
+
panel's own `setFields()` is not pushed and lasts until the view's next change. Grouping and board
|
|
2680
|
+
changes are only emitted — the host applies them.
|
|
2376
2681
|
<!-- /doc -->
|
|
2377
2682
|
|
|
2378
2683
|
<!-- doc:pagination -->
|
|
2379
2684
|
### Pagination
|
|
2380
2685
|
|
|
2381
|
-
The pager for a server-backed list
|
|
2382
|
-
`
|
|
2383
|
-
|
|
2384
|
-
|
|
2686
|
+
The pager for a server-backed list. It owns no data: `getState()` and every `change` hand back
|
|
2687
|
+
the `page`, `pageSize`, and zero-based `offset = (page - 1) * pageSize` a query needs. `page` is
|
|
2688
|
+
1-based and always clamped into `[1, pages]`; a `total` of 0 is treated as one empty page rather
|
|
2689
|
+
than zero pages.
|
|
2690
|
+
|
|
2691
|
+
The ZeyOS bindings page differently — zero-based, `pageSize: 50`, appending pages above zero — so
|
|
2692
|
+
driving `zeyosTable()` (or `zeyosView()`) takes one mapping in the `change` listener:
|
|
2693
|
+
|
|
2694
|
+
```js
|
|
2695
|
+
const binding = zeyosTable(client, 'contacts', { pageSize: 25 });
|
|
2696
|
+
const pager = new Pagination(host, { pageSize: binding.pageSize });
|
|
2697
|
+
pager.on('change', async ({ detail }) => {
|
|
2698
|
+
const result = await binding.load({ page: detail.page - 1, pageSize: detail.pageSize, replace: true });
|
|
2699
|
+
pager.setTotal(result.count ?? 0); // silent, so it cannot loop
|
|
2700
|
+
});
|
|
2701
|
+
```
|
|
2702
|
+
|
|
2703
|
+
In `mode: 'loadmore'` each `change` is one `binding.loadMore()`, which appends.
|
|
2385
2704
|
|
|
2386
2705
|
- **Options** — `page: 1`, `pageSize: 25`, `total: 0`, `pageSizes: [25, 50, 100]`, `siblings: 1`,
|
|
2387
2706
|
`boundaries: 1`, `showPageSize: true`, `showSummary: true` (renders "26–50 of 312"),
|
|
@@ -2396,8 +2715,9 @@ treated as one empty page rather than zero pages.
|
|
|
2396
2715
|
- **Semantics** — `<nav aria-label="Pagination">`; the current page carries `aria-current="page"`;
|
|
2397
2716
|
previous/next are labelled icon buttons that go `disabled` at the ends; the ellipses are
|
|
2398
2717
|
`aria-hidden` spans, not buttons.
|
|
2399
|
-
- **Appearance** — the
|
|
2400
|
-
|
|
2718
|
+
- **Appearance** — the summary and the page-size select sit together at the start of the bar,
|
|
2719
|
+
the pager alone at the end; the current page is the neutral `--zx-color-bg-current` fill with a
|
|
2720
|
+
semibold figure: where you are, not a call to action.
|
|
2401
2721
|
<!-- /doc -->
|
|
2402
2722
|
|
|
2403
2723
|
<!-- doc:navigation-bar -->
|
|
@@ -2431,21 +2751,56 @@ inline; every rail state opens descendants toward the workspace in anchored flyo
|
|
|
2431
2751
|
|
|
2432
2752
|
- **Options** — `items`, `active`, `expanded`, `collapsed`, `collapsible` (`false` removes the
|
|
2433
2753
|
minimise and expand buttons), `orientation: 'vertical'|'horizontal'`,
|
|
2434
|
-
`side: 'left'|'right'|'top'|'bottom'`, `
|
|
2754
|
+
`side: 'left'|'right'|'top'|'bottom'`, `layout: 'tree'|'modules'`, `module`, `moduleBar`
|
|
2755
|
+
(`{appearance, labels: 'auto'|'beside'|'below'|'hidden', allModules, label}` for the module
|
|
2756
|
+
strip), `label`,
|
|
2435
2757
|
`header`, `footer`, `railHeader`, `railFooter`, `openDelay: 80`, `closeDelay: 160`, and
|
|
2436
2758
|
`renderIcon(item, context)`. Icon values may also be names, nodes, components, or factories.
|
|
2437
|
-
- **
|
|
2438
|
-
|
|
2759
|
+
- **Sections** — an item with `section: true` is a quiet heading over its `children`, which stay
|
|
2760
|
+
at its own level: never indented, collapsed, focused, or selected, and it needs no `id`.
|
|
2761
|
+
`actions` (a node) adds trailing controls such as an add button. Sections replace stacking
|
|
2762
|
+
several sidebars: one `<nav>`, one keyboard sequence, one rail. On a rail a section becomes a
|
|
2763
|
+
rule; in a flyout, a caption.
|
|
2764
|
+
- **Modules layout** — `layout: 'modules'` reads the top level as modules (`ModuleItem`s: `tone`
|
|
2765
|
+
or `color`, `badge`, `pinned`, `group`) and draws them as a `ModuleBar` strip beside a panel
|
|
2766
|
+
with one module's menu. A module with `children`, `header` (a node above its menu, such as its
|
|
2767
|
+
primary action), or `content` (a node below it, such as a tree or a history) is a panel module:
|
|
2768
|
+
choosing its tile shows its panel and fires a cancelable `modulechange`; any other module is a
|
|
2769
|
+
destination and fires `select`. The panel starts on `module`, else the module holding `active`,
|
|
2770
|
+
and follows `setActive()` into another module. A `section` at the top level heads its modules on
|
|
2771
|
+
the strip.
|
|
2772
|
+
- **Presentations** — the modules layout shows `'expanded'` (both menus: the strip names its
|
|
2773
|
+
modules beside their tiles, and the side menu shows), `'compact'` (the module menu collapsed to
|
|
2774
|
+
icons beside the side menu), or `'collapsed'` (the icon strip alone; every module's menu opens as
|
|
2775
|
+
a flyout beside its tile). A small three-way switch at the foot of the strip, above
|
|
2776
|
+
`railFooter`, chooses between them: an APG radio group ("Navigation layout") of one pictogram per
|
|
2777
|
+
presentation, in a row while the modules are named and a column on the icon strip; arrow keys
|
|
2778
|
+
move and choose, Home and End jump. `presentationSwitch: false` leaves it out when the choice
|
|
2779
|
+
lives in a menu instead: `presentationChoice({label})` returns the same choice as a menu
|
|
2780
|
+
`choice` entry (`account.setItems([sidebar.presentationChoice(), …])`; rebuild it after
|
|
2781
|
+
`presentationchange`), and `presentationGlyph(name)` is its pictogram. `presentations` (default all three) decides which it offers;
|
|
2782
|
+
`presentation` sets the first one; `getPresentation()`, `setPresentation()`, and
|
|
2783
|
+
`togglePresentation()` (the next one, wrapping — Mod+B in an application) drive it, and
|
|
2784
|
+
`presentationchange {presentation, previous}` reports it. `moduleBar.labels: 'hidden'` never
|
|
2785
|
+
names the modules. `railHeader`/`railFooter` are the strip's head and foot in every
|
|
2786
|
+
presentation. The arrow toward the panel moves from a tile into its menu, and Left Arrow on a
|
|
2787
|
+
top-level row of the menu returns to the tile.
|
|
2788
|
+
- **Methods** — `setItems()`, `getItems()`, `setActive()`, `setBadge(id, badge)`, `setExpanded()`,
|
|
2789
|
+
`getExpanded()`, `toggleBranch()`, `expand()`, `collapse()`, `setCollapsed()`, `isCollapsed()`,
|
|
2790
|
+
`setModule(id, {silent})`, `getModule()`, `setModuleLabels(labels)`, `getPresentation()`,
|
|
2791
|
+
`setPresentation(presentation, {silent})`, `togglePresentation()`, `presentationChoice({label})`,
|
|
2439
2792
|
`setLayout({orientation, side})`, `openFlyout(id, {focus})`, `closeFlyout()`,
|
|
2440
2793
|
`closeAllFlyouts()`, `isFlyoutOpen()`, `focus()`, `destroy()`. `getItems()` returns copies of
|
|
2441
2794
|
the normalized tree; a leaf carries no `children` property.
|
|
2442
2795
|
- **Events** — cancelable `select {item, id, value, href, event}`,
|
|
2443
|
-
`branchchange {item, id, expanded, ids}`, `collapsechange {collapsed}`,
|
|
2444
|
-
`flyoutchange {item, id, open}
|
|
2796
|
+
`branchchange {item, id, expanded, ids}`, `collapsechange {collapsed}`,
|
|
2797
|
+
`flyoutchange {item, id, open}`, cancelable `modulechange {item, id, previous}`, and
|
|
2798
|
+
`presentationchange {presentation, previous}`.
|
|
2445
2799
|
- **Presentation state** — horizontal navigation is always effectively minimized and never exposes
|
|
2446
2800
|
an expand control. Switching layouts does not erase the saved vertical collapsed preference or
|
|
2447
|
-
expanded branch IDs. `AppSidebar` is the
|
|
2448
|
-
`collapsed: true` or `orientation: 'horizontal'` for its rail presentations
|
|
2801
|
+
expanded branch IDs. `AppSidebar` is the one component that owns an application's navigation
|
|
2802
|
+
tree; use `collapsed: true` or `orientation: 'horizontal'` for its rail presentations, and
|
|
2803
|
+
`layout: 'modules'` for the module strip it composes from `ModuleBar`.
|
|
2449
2804
|
- **Current state** — the current page is neutral: the `--zx-color-bg-current` fill, the full
|
|
2450
2805
|
text colour and a semibold label, in the tree, the rail and every flyout; hover is a lighter
|
|
2451
2806
|
step of the same overlay. A closed branch (or a rail item) over the active route takes the
|
|
@@ -2462,11 +2817,81 @@ inline; every rail state opens descendants toward the workspace in anchored flyo
|
|
|
2462
2817
|
link or callback is disabled in every presentation.
|
|
2463
2818
|
<!-- /doc -->
|
|
2464
2819
|
|
|
2820
|
+
<!-- doc:module-bar -->
|
|
2821
|
+
### ModuleBar — the module level of an application
|
|
2822
|
+
|
|
2823
|
+
One colour-coded tile per module, in a vertical strip or a horizontal bar: the level above an
|
|
2824
|
+
application's menus. Use it on its own — the classic module bar in an application header, beside
|
|
2825
|
+
a tree-layout `AppSidebar` — or through `AppSidebar`'s `layout: 'modules'`, which composes it.
|
|
2826
|
+
|
|
2827
|
+
```js
|
|
2828
|
+
import { ModuleBar } from '/assets/zx.esm.js';
|
|
2829
|
+
|
|
2830
|
+
const bar = new ModuleBar(null, {
|
|
2831
|
+
items: [
|
|
2832
|
+
{ id: 'mail', label: 'Mail', icon: 'envelope', tone: 'blue', badge: 7, children: [
|
|
2833
|
+
{ id: 'inbox', label: 'Inbox', href: '#inbox' }, { id: 'sent', label: 'Sent', href: '#sent' }
|
|
2834
|
+
] },
|
|
2835
|
+
{ id: 'billing', label: 'Billing', icon: 'file-invoice', color: '#535494', href: '#billing' }
|
|
2836
|
+
],
|
|
2837
|
+
active: 'inbox',
|
|
2838
|
+
onselect: (event) => { event.preventDefault(); route(event.detail.id); }
|
|
2839
|
+
});
|
|
2840
|
+
bar.setBadge('mail', 8);
|
|
2841
|
+
```
|
|
2842
|
+
|
|
2843
|
+
- **Items** (`ModuleItem`) — the `AppNavItem` shape plus `tone` (a categorical tone that follows
|
|
2844
|
+
the theme) or `color` (any CSS colour; it wins), `pinned: true|number` (when any module is
|
|
2845
|
+
pinned, unpinned ones are under "All modules" only), `group` (the catalogue heading), and
|
|
2846
|
+
`description`. Children are the module's menu. A `section` at the top level heads its modules:
|
|
2847
|
+
a caption over them while names are printed beside the tiles, a short rule between groups on an
|
|
2848
|
+
icon strip or bar, and their group in the catalogue.
|
|
2849
|
+
- **Options** — `items`, `active` (a module or anything in a module's menu),
|
|
2850
|
+
`orientation: 'vertical'|'horizontal'`, `side`, `appearance: 'tint'|'solid'|'plain'` (the glyph
|
|
2851
|
+
on a tint of the module colour; the solid identity tile; monochrome glyphs),
|
|
2852
|
+
`labels: 'hidden'|'below'|'beside'` (names under the tiles of a vertical strip, or beside them —
|
|
2853
|
+
a navigation column of its own, a row per module with its count at the end),
|
|
2854
|
+
`menus: 'flyout'|'none'`, `expanded`, `controls`, `allModules: 'auto'|'always'|'never'`,
|
|
2855
|
+
`header`, `footer`, `label` (default "Modules"), `openDelay`, `closeDelay`, `renderIcon`.
|
|
2856
|
+
- **Menus** — with `menus: 'flyout'` a module's children open beside its tile on hover, focus,
|
|
2857
|
+
click, or the arrow toward the workspace, exactly like a rail. With `menus: 'none'` an adjacent
|
|
2858
|
+
panel the application owns shows them: every module then fires `select`, `setExpanded(id)`
|
|
2859
|
+
names the module the panel shows, and each tile with a menu reports `aria-expanded` and
|
|
2860
|
+
`aria-controls` the `controls` id.
|
|
2861
|
+
- **Overflow** — modules that do not fit move behind the "All modules" button; the current module
|
|
2862
|
+
(and the expanded one) always stays. The catalogue lists every module as a labelled tile,
|
|
2863
|
+
grouped by `group`, with a filter field past twelve modules. `partitionModules(items, capacity,
|
|
2864
|
+
keep, reserve)` is the pure function deciding what shows; `reserve` keeps the button's slot for
|
|
2865
|
+
`allModules: 'always'`.
|
|
2866
|
+
- **Methods** — `setItems()`, `getItems()`, `setActive()`, `setBadge(id, badge)` (in place, on the
|
|
2867
|
+
tile, in its menu, and in the catalogue), `setExpanded(id)`, `setLabels(labels)` (how a column
|
|
2868
|
+
expands to names and collapses to icons), `setLayout({orientation, side})`,
|
|
2869
|
+
`openFlyout()`, `closeFlyout()`, `closeAllFlyouts()`, `isFlyoutOpen()`, `openAllModules()`,
|
|
2870
|
+
`closeAllModules()`, `isAllModulesOpen()`, `focus()`, `destroy()`.
|
|
2871
|
+
- **Events** — cancelable `select {item, id, value, href, event}`, `flyoutchange {item, id, open}`,
|
|
2872
|
+
`overflowchange {visible, overflow}`, and `allmodules {open}`.
|
|
2873
|
+
- **Keyboard** — the bar is one Tab stop, on the current module. Arrow keys along the bar move
|
|
2874
|
+
between tiles and wrap; Home and End go to the first and last control; typing jumps to the next
|
|
2875
|
+
module whose name starts with the letters typed; the arrow toward the workspace opens a menu and
|
|
2876
|
+
moves into it; Escape, or the arrow back toward the strip, closes a menu onto its tile; Escape
|
|
2877
|
+
closes the catalogue onto its button, and so does tabbing out of it. In the catalogue, arrows
|
|
2878
|
+
move across the grid, typing goes to the filter field (past twelve modules), and Enter chooses.
|
|
2879
|
+
- **Semantics** — a `<nav>` of native links and buttons, never a menu or menubar. The module that
|
|
2880
|
+
is or holds the current page carries `aria-current` (`page` or `true`); an icon-only tile's
|
|
2881
|
+
accessible name is its label, with its count ("Mail (7)"), and it has a tooltip. Current is
|
|
2882
|
+
the neutral `--zx-color-bg-current` fill behind the tile, never the accent or an edge bar.
|
|
2883
|
+
- **Geometry** — a target is one control plus a space step, a tile one control less one, so
|
|
2884
|
+
`--zx-control-height` resizes the bar; a coarse pointer keeps a 40px target.
|
|
2885
|
+
<!-- /doc -->
|
|
2886
|
+
|
|
2465
2887
|
<!-- doc:kernel -->
|
|
2466
2888
|
### Core helpers
|
|
2467
2889
|
|
|
2468
2890
|
- **Component** — the base class: `on`/`off`/`once`/`emit`, `listen`, `toElement`, `msg`,
|
|
2469
|
-
`destroy`, and the static `Component.from(el)`.
|
|
2891
|
+
`destroy`, and the static `Component.from(el)`. `msg(key, ...args)` always returns text: the
|
|
2892
|
+
instance's `msg` option (a `{[lang]: {…}}` table for the active language, then flat keys), else
|
|
2893
|
+
the host translator, else the class's built-in `msg` defaults, else the default English
|
|
2894
|
+
catalogue, and only then the key itself; `%1`, `%2`, … are filled from `args`.
|
|
2470
2895
|
- **DOM** — `h(tag, props, ...children)`, `h.raw(html)`, `htmlEscape`, `resolveElement`.
|
|
2471
2896
|
`__(tag#id.class, properties, content)` is Zx's compact DOM builder and public alias for
|
|
2472
2897
|
`compactElement()`. It preserves `D*` data, `S*` style, boolean/built-in property, attribute,
|
|
@@ -2483,7 +2908,9 @@ inline; every rail state opens descendants toward the workspace in anchored flyo
|
|
|
2483
2908
|
`{update, destroy}`. It anchors a manual popover with CSS anchor positioning where the browser
|
|
2484
2909
|
supports it and a scroll/resize-tracked fallback where it does not. Options are resolved once,
|
|
2485
2910
|
so changing a placement means `destroy()` and a fresh `position()` call.
|
|
2486
|
-
- **i18n** — `setTranslator`, `setLanguage`, `getLanguage`, `translate`, `printf
|
|
2911
|
+
- **i18n** — `setTranslator`, `setLanguage`, `getLanguage`, `translate`, `printf`, and
|
|
2912
|
+
`defaultMessages`, the frozen English catalogue of every component key (`table.empty` → "No
|
|
2913
|
+
records") that a host copies as the starting point for a locale.
|
|
2487
2914
|
- **Dates** — `formatDate(d, fmt)`, `parseDate(s, fmt)` (tokens
|
|
2488
2915
|
`%d %m %Y %y %H %M %S %a %A %b %B %s`), `clampDate`, `isSameDay`, `addDays`, `addMonths`,
|
|
2489
2916
|
`getWeekStart`.
|
|
@@ -2606,12 +3033,20 @@ entry point as the components. See also the Kernel section for `Component`, `h`,
|
|
|
2606
3033
|
`copyToClipboard(text)` → `Promise<boolean>` uses the async Clipboard API and falls back to a
|
|
2607
3034
|
hidden textarea, never rejecting.
|
|
2608
3035
|
- **Translation** — `setTranslator(fn)`, `setLanguage(lang)`, `getLanguage()`,
|
|
2609
|
-
`translate(key, args)`, `printf(str, args)` (`%
|
|
2610
|
-
built-in strings through `
|
|
2611
|
-
`
|
|
3036
|
+
`translate(key, args)`, `printf(str, args)` (`%1`, `%2`, … placeholders), `defaultMessages`.
|
|
3037
|
+
Every component resolves its built-in strings through `msg()`, so one translator covers the
|
|
3038
|
+
library: per-instance `msg` option → `setTranslator()` function → the component's built-in
|
|
3039
|
+
defaults → the default English catalogue → the key. The translator answers one key at a time;
|
|
3040
|
+
null, undefined, or the key itself means "not translated" and English shows, so an application
|
|
3041
|
+
without a translator never sees a raw key. `defaultMessages` is that catalogue — copy it to
|
|
3042
|
+
start a locale (`setTranslator((key) => de[key])`). `setLanguage()` also selects the language
|
|
3043
|
+
table of a `msg` option and drives locale-dependent behaviour such as the first day of the week.
|
|
2612
3044
|
- **Keyboard** — `focusTrap(container)` → `{activate, release}` confines Tab to a container;
|
|
2613
|
-
`rovingTabindex(container, itemSelector, {orientation, wrap})` → `{focusFirst,
|
|
2614
|
-
destroy}` keeps one tab stop in a group
|
|
3045
|
+
`rovingTabindex(container, itemSelector, {orientation, wrap, rtl: 'auto'})` → `{focusFirst,
|
|
3046
|
+
focusLast, destroy}` keeps one tab stop in a group — horizontal arrows follow `dir="rtl"`, and a
|
|
3047
|
+
text field, select, or contenteditable item keeps Home/End and its arrows (an arrow leaves a text
|
|
3048
|
+
field only from the edge of its text); toggling a `<fieldset disabled>` around items inside the
|
|
3049
|
+
container moves the tab stop off them; `typeahead(getItems, onMatch)` →
|
|
2615
3050
|
`(event|string) => void` resolves buffered printable keystrokes to an item (500 ms buffer,
|
|
2616
3051
|
repeated-letter cycling).
|
|
2617
3052
|
<!-- /doc -->
|
|
@@ -2668,8 +3103,13 @@ in component CSS.
|
|
|
2668
3103
|
(`--zx-dur-fast` 120ms, `--zx-dur-base` 180ms — both 0ms in a reduced-motion region — and the
|
|
2669
3104
|
curves `--zx-ease`, `--zx-ease-spring`, `--zx-ease-drawer`).
|
|
2670
3105
|
- **Tier 2** — the semantic set, the only tokens components may use: `--zx-color-*`,
|
|
2671
|
-
`--zx-control-*`, `--zx-focus-ring`, `--zx-
|
|
2672
|
-
|
|
3106
|
+
`--zx-control-*`, `--zx-focus-ring`, `--zx-surface-shadow` (the contact shadow of a resting
|
|
3107
|
+
surface: a panel, an outlined card in a lane, a slider thumb), `--zx-overlay-shadow`,
|
|
3108
|
+
`--zx-drag-shadow` (an item being dragged; its own value in dark, with a faint light rim). The
|
|
3109
|
+
lint also rejects the accent ramp `--zx-accent-*`, the module palette `--zx-module-*`, and the
|
|
3110
|
+
raw `--zx-shadow-*` steps in component and layout CSS, and any `outline: 0`/`outline: none`:
|
|
3111
|
+
forced-colors mode drops the box-shadow focus ring, so components make the outline transparent
|
|
3112
|
+
and `styles/base.css` repaints it in a system colour there. The roles that carry the visual
|
|
2673
3113
|
language:
|
|
2674
3114
|
- **Columns.** `--zx-color-bg-surface` is the working canvas; `--zx-color-bg-chrome` is the
|
|
2675
3115
|
navigation and inspector tone beside it (one step below the canvas in light, between page and
|