@zeyos/zx 4.2.0 → 4.3.4

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/README.md +5 -3
  3. package/dist/types/components/calendar/calendar-model.d.ts +22 -0
  4. package/dist/types/components/calendar/calendar.d.ts +19 -2
  5. package/dist/types/components/card-view/card-view.d.ts +1 -1
  6. package/dist/types/components/card-view/record-card.d.ts +47 -5
  7. package/dist/types/components/dialog/dialog.d.ts +6 -1
  8. package/dist/types/components/dropdown/dropdown.d.ts +5 -0
  9. package/dist/types/components/kanban-view/kanban-policy.d.ts +268 -0
  10. package/dist/types/components/kanban-view/kanban-view.d.ts +669 -24
  11. package/dist/types/components/launcher/launcher.d.ts +5 -0
  12. package/dist/types/components/message/message.d.ts +9 -1
  13. package/dist/types/components/modal/modal.d.ts +44 -7
  14. package/dist/types/components/sheet/sheet.d.ts +22 -12
  15. package/dist/types/core/component.d.ts +6 -1
  16. package/dist/types/core/overlay-host.d.ts +12 -0
  17. package/dist/types/index.d.ts +2 -1
  18. package/dist/zx-zeyos.esm.js +173 -43
  19. package/dist/zx-zeyos.esm.min.js +1 -1
  20. package/dist/zx.css +876 -230
  21. package/dist/zx.esm.js +2053 -338
  22. package/dist/zx.esm.min.js +3 -3
  23. package/dist/zx.global.js +2053 -338
  24. package/dist/zx.global.min.js +3 -3
  25. package/dist/zx.min.css +1 -1
  26. package/docs/api.json +250 -2
  27. package/docs/llms.md +67 -48
  28. package/docs/llms.txt +1 -3
  29. package/package.json +1 -2
  30. package/src/components/account-menu/account-menu.css +1 -1
  31. package/src/components/app-rail/app-rail.css +1 -1
  32. package/src/components/calendar/calendar-model.js +45 -0
  33. package/src/components/calendar/calendar.css +96 -34
  34. package/src/components/calendar/calendar.js +122 -29
  35. package/src/components/card-view/card-view.css +211 -9
  36. package/src/components/card-view/card-view.js +7 -7
  37. package/src/components/card-view/record-card.js +92 -19
  38. package/src/components/context-menu/context-menu.css +2 -2
  39. package/src/components/context-menu/context-menu.js +5 -1
  40. package/src/components/date-picker/date-picker.css +18 -11
  41. package/src/components/datebox/date-range-box.css +4 -2
  42. package/src/components/datebox/datebox.css +4 -2
  43. package/src/components/dialog/dialog.css +4 -3
  44. package/src/components/dialog/dialog.js +2 -1
  45. package/src/components/dropdown/dropdown.css +4 -2
  46. package/src/components/dropdown/dropdown.js +7 -2
  47. package/src/components/kanban-view/kanban-policy.js +412 -0
  48. package/src/components/kanban-view/kanban-view.css +480 -36
  49. package/src/components/kanban-view/kanban-view.js +1514 -235
  50. package/src/components/launcher/launcher.css +10 -8
  51. package/src/components/launcher/launcher.js +9 -2
  52. package/src/components/menu-button/menu-button.css +2 -2
  53. package/src/components/message/message.css +15 -25
  54. package/src/components/message/message.js +17 -11
  55. package/src/components/modal/modal.css +6 -4
  56. package/src/components/modal/modal.js +99 -25
  57. package/src/components/select/select.css +9 -7
  58. package/src/components/sheet/sheet.css +25 -7
  59. package/src/components/sheet/sheet.js +43 -23
  60. package/src/components/table/table.css +5 -3
  61. package/src/components/tag-picker/tag-picker.css +5 -3
  62. package/src/components/tooltip/tooltip.css +4 -2
  63. package/src/components/tooltip/tooltip.js +4 -1
  64. package/src/core/component.js +8 -3
  65. package/src/core/overlay-host.js +23 -0
  66. package/src/index.js +8 -3
  67. package/styles/tokens/dark.css +4 -0
  68. package/styles/tokens/semantic.css +83 -8
  69. package/DESIGN-SYSTEM.md +0 -182
package/docs/api.json CHANGED
@@ -1468,7 +1468,7 @@
1468
1468
  {
1469
1469
  "name": "minCardWidth",
1470
1470
  "type": "number|string",
1471
- "default": "'18rem'",
1471
+ "default": "'15rem'",
1472
1472
  "description": "Responsive minimum card width."
1473
1473
  },
1474
1474
  {
@@ -2750,6 +2750,12 @@
2750
2750
  "type": "Node|string|number|{toElement: () => Node|null}|null",
2751
2751
  "default": "null",
2752
2752
  "description": "Body content."
2753
+ },
2754
+ {
2755
+ "name": "scope",
2756
+ "type": "Element|string|null",
2757
+ "default": "null",
2758
+ "description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
2753
2759
  }
2754
2760
  ],
2755
2761
  "events": [
@@ -3082,6 +3088,12 @@
3082
3088
  "type": "boolean",
3083
3089
  "default": "false",
3084
3090
  "description": "Whether a click within the panel closes it."
3091
+ },
3092
+ {
3093
+ "name": "scope",
3094
+ "type": "Element|string|null",
3095
+ "default": "null",
3096
+ "description": "Element whose nearest Zx theme scope owns the panel; defaults to the anchor."
3085
3097
  }
3086
3098
  ],
3087
3099
  "events": [
@@ -4318,6 +4330,132 @@
4318
4330
  "default": "'local'",
4319
4331
  "description": "Whether accepted moves update local data."
4320
4332
  },
4333
+ {
4334
+ "name": "dragFrom",
4335
+ "type": "'card'|'handle'",
4336
+ "default": "'card'",
4337
+ "description": "Whether the whole card or only its handle starts a pointer drag. Touch drags from the card body require a long press so the board still scrolls."
4338
+ },
4339
+ {
4340
+ "name": "multiMove",
4341
+ "type": "boolean",
4342
+ "default": "true",
4343
+ "description": "Move the whole selection when a selected card is picked up."
4344
+ },
4345
+ {
4346
+ "name": "autoScroll",
4347
+ "type": "boolean",
4348
+ "default": "true",
4349
+ "description": "Scroll the board and columns while dragging near an edge."
4350
+ },
4351
+ {
4352
+ "name": "wipPolicy",
4353
+ "type": "'warn'|'block'",
4354
+ "default": "'warn'",
4355
+ "description": "Whether exceeding a limit warns or refuses."
4356
+ },
4357
+ {
4358
+ "name": "rules",
4359
+ "type": "KanbanRule[]",
4360
+ "default": "[]",
4361
+ "description": "Ordered visual card rules."
4362
+ },
4363
+ {
4364
+ "name": "search",
4365
+ "type": "string",
4366
+ "default": "''",
4367
+ "description": "Board search text."
4368
+ },
4369
+ {
4370
+ "name": "searchFields",
4371
+ "type": "string[]|null",
4372
+ "default": "null",
4373
+ "description": "Field ids searched; null searches visible fields plus the title and subtitle sources."
4374
+ },
4375
+ {
4376
+ "name": "filter",
4377
+ "type": "((record:KanbanRecord,index:number)=>boolean)|null",
4378
+ "default": "null",
4379
+ "description": "Extra card predicate."
4380
+ },
4381
+ {
4382
+ "name": "searchControl",
4383
+ "type": "boolean",
4384
+ "default": "false",
4385
+ "description": "Render the built-in board search control."
4386
+ },
4387
+ {
4388
+ "name": "history",
4389
+ "type": "boolean",
4390
+ "default": "true",
4391
+ "description": "Record committed local moves for undo and redo."
4392
+ },
4393
+ {
4394
+ "name": "historyLimit",
4395
+ "type": "number",
4396
+ "default": "50",
4397
+ "description": "Maximum remembered move steps."
4398
+ },
4399
+ {
4400
+ "name": "historyControls",
4401
+ "type": "boolean",
4402
+ "default": "false",
4403
+ "description": "Render built-in undo and redo controls."
4404
+ },
4405
+ {
4406
+ "name": "columnHeight",
4407
+ "type": "string|null",
4408
+ "default": "null",
4409
+ "description": "CSS length turning each column into a scroll region."
4410
+ },
4411
+ {
4412
+ "name": "allowAdd",
4413
+ "type": "boolean|((column:KanbanColumn,swimlane:KanbanSwimlane|null)=>boolean)",
4414
+ "default": "false",
4415
+ "description": "Render a per-column add control that emits `recordadd`."
4416
+ },
4417
+ {
4418
+ "name": "contextMenu",
4419
+ "type": "boolean|MenuItem[]|((context:KanbanCardContext)=>MenuItem[])",
4420
+ "default": "false",
4421
+ "description": "Card context menu. `true` offers move commands; an array or function appends application items."
4422
+ },
4423
+ {
4424
+ "name": "renderCard",
4425
+ "type": "((context:KanbanCardContext)=>Node|null|undefined)|null",
4426
+ "default": "null",
4427
+ "description": "Replaces card content."
4428
+ },
4429
+ {
4430
+ "name": "renderColumnHeader",
4431
+ "type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
4432
+ "default": "null",
4433
+ "description": "Replaces column header content."
4434
+ },
4435
+ {
4436
+ "name": "renderSwimlaneHeader",
4437
+ "type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
4438
+ "default": "null",
4439
+ "description": "Replaces lane header content."
4440
+ },
4441
+ {
4442
+ "name": "renderColumnEmpty",
4443
+ "type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
4444
+ "default": "null",
4445
+ "description": "Empty-column placeholder."
4446
+ },
4447
+ {
4448
+ "name": "renderDragPreview",
4449
+ "type": "((context:{records:KanbanRecord[],ids:unknown[],count:number})=>Node|null|undefined)|null",
4450
+ "default": "null",
4451
+ "description": "Replaces the floating drag preview."
4452
+ },
4453
+ {
4454
+ "name": "labels",
4455
+ "type": "Record<string,string>",
4456
+ "default": "{}",
4457
+ "description": "Overrides for every visible and announced string."
4458
+ },
4321
4459
  {
4322
4460
  "name": "columnOrder",
4323
4461
  "type": "string[]",
@@ -4373,10 +4511,30 @@
4373
4511
  "payload": "Record<string,unknown>",
4374
4512
  "description": ""
4375
4513
  },
4514
+ {
4515
+ "name": "movereject",
4516
+ "payload": "Record<string,unknown>",
4517
+ "description": ""
4518
+ },
4376
4519
  {
4377
4520
  "name": "recordaction",
4378
4521
  "payload": "Record<string,unknown>",
4379
4522
  "description": ""
4523
+ },
4524
+ {
4525
+ "name": "recordadd",
4526
+ "payload": "Record<string,unknown>",
4527
+ "description": ""
4528
+ },
4529
+ {
4530
+ "name": "searchchange",
4531
+ "payload": "{search:string}",
4532
+ "description": ""
4533
+ },
4534
+ {
4535
+ "name": "historychange",
4536
+ "payload": "{canUndo:boolean,canRedo:boolean,depth:{undo:number,redo:number}}",
4537
+ "description": ""
4380
4538
  }
4381
4539
  ],
4382
4540
  "methods": [
@@ -4480,7 +4638,73 @@
4480
4638
  "name": "moveRecord",
4481
4639
  "signature": "moveRecord(id, destination)",
4482
4640
  "returns": "this",
4483
- "description": "Proposes and, in local mode, commits a record move. Destination acceptance is checked before a cancelable `recordmove` event. WIP limits are reported but remain advisory."
4641
+ "description": "Proposes and, in local mode, commits a record move. Equivalent to `moveRecords([id], …)`."
4642
+ },
4643
+ {
4644
+ "name": "moveRecords",
4645
+ "signature": "moveRecords(ids, destination)",
4646
+ "returns": "this",
4647
+ "description": "Proposes and, in local mode, commits an atomic move of one or more records. Destination eligibility, transition rules, and work-in-progress policy are all checked before a single cancelable `recordmove` event; the move commits completely or not at all, and the moving records keep their relative order at the destination."
4648
+ },
4649
+ {
4650
+ "name": "canUndo",
4651
+ "signature": "canUndo()",
4652
+ "returns": "boolean",
4653
+ "description": "Whether a committed local move can be reverted."
4654
+ },
4655
+ {
4656
+ "name": "canRedo",
4657
+ "signature": "canRedo()",
4658
+ "returns": "boolean",
4659
+ "description": "Whether a reverted local move can be reapplied."
4660
+ },
4661
+ {
4662
+ "name": "undo",
4663
+ "signature": "undo()",
4664
+ "returns": "this",
4665
+ "description": "Reverts the last committed local move, restoring the exact record order and selection."
4666
+ },
4667
+ {
4668
+ "name": "redo",
4669
+ "signature": "redo()",
4670
+ "returns": "this",
4671
+ "description": "Reapplies the last reverted local move."
4672
+ },
4673
+ {
4674
+ "name": "clearHistory",
4675
+ "signature": "clearHistory()",
4676
+ "returns": "this",
4677
+ "description": "Empties both history stacks."
4678
+ },
4679
+ {
4680
+ "name": "getSearch",
4681
+ "signature": "getSearch()",
4682
+ "returns": "string",
4683
+ "description": "Returns the current board search text."
4684
+ },
4685
+ {
4686
+ "name": "setSearch",
4687
+ "signature": "setSearch(search, [options])",
4688
+ "returns": "this",
4689
+ "description": "Narrows visible cards to those matching a search. Hidden records keep their position in the data, so a card dropped between two visible cards still lands between them."
4690
+ },
4691
+ {
4692
+ "name": "setFilter",
4693
+ "signature": "setFilter(filter)",
4694
+ "returns": "this",
4695
+ "description": "Replaces the extra card predicate. Pass null to clear it."
4696
+ },
4697
+ {
4698
+ "name": "getVisibleRecords",
4699
+ "signature": "getVisibleRecords()",
4700
+ "returns": "KanbanRecord[]",
4701
+ "description": "Returns the records currently rendered, in board order."
4702
+ },
4703
+ {
4704
+ "name": "setData",
4705
+ "signature": "setData(records)",
4706
+ "returns": "this",
4707
+ "description": "Replaces every record. History is dropped because its snapshots describe records the host has just replaced, and reinstating them would resurrect data the application no longer owns."
4484
4708
  },
4485
4709
  {
4486
4710
  "name": "getViewState",
@@ -4587,6 +4811,12 @@
4587
4811
  "type": "false|{move?: string, open?: string, close?: string}",
4588
4812
  "default": null,
4589
4813
  "description": "Visible keyboard hints, or false."
4814
+ },
4815
+ {
4816
+ "name": "scope",
4817
+ "type": "Element|string|null",
4818
+ "default": "null",
4819
+ "description": "Element whose nearest Zx theme scope owns an internally created launcher; defaults to the opener."
4590
4820
  }
4591
4821
  ],
4592
4822
  "events": [
@@ -4969,6 +5199,12 @@
4969
5199
  "type": "boolean",
4970
5200
  "default": "true",
4971
5201
  "description": "Whether to render a close button."
5202
+ },
5203
+ {
5204
+ "name": "scope",
5205
+ "type": "Element|string|null",
5206
+ "default": "null",
5207
+ "description": "Element whose nearest Zx theme scope owns a floating toast."
4972
5208
  }
4973
5209
  ],
4974
5210
  "events": [],
@@ -5005,6 +5241,12 @@
5005
5241
  "type": "boolean",
5006
5242
  "default": "false",
5007
5243
  "description": "Whether to destroy the modal after it closes."
5244
+ },
5245
+ {
5246
+ "name": "scope",
5247
+ "type": "Element|string|null",
5248
+ "default": "null",
5249
+ "description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
5008
5250
  }
5009
5251
  ],
5010
5252
  "events": [
@@ -6985,6 +7227,12 @@
6985
7227
  "type": "boolean",
6986
7228
  "default": "false",
6987
7229
  "description": "Whether to destroy the sheet after it closes."
7230
+ },
7231
+ {
7232
+ "name": "scope",
7233
+ "type": "Element|string|null",
7234
+ "default": "null",
7235
+ "description": "Element whose nearest Zx theme scope owns a floating sheet; defaults to the opener."
6988
7236
  }
6989
7237
  ],
6990
7238
  "events": [
package/docs/llms.md CHANGED
@@ -31,6 +31,8 @@ const t = new Table(target, options);
31
31
  (always an object, e.g. `{ value, item }`).
32
32
  - Every `emit` also dispatches a **bubbling, composed** `zx-<type>` `CustomEvent` on `t.el`, so
33
33
  `t.el.addEventListener('zx-change', …)` and event delegation work.
34
+ - Mutation boundaries may opt into honoring cancellation of that mirrored DOM event; Kanban's
35
+ `zx-recordmove` does, so delegated hosts can veto a move without a component reference.
34
36
  - `t.destroy()` aborts all listeners (one AbortController), unregisters the element, and removes an
35
37
  owned root. `Component.from(el)` returns the component for a root element (replaces the legacy
36
38
  `el.retrieve('com')`).
@@ -80,14 +82,19 @@ ambient canvas layer; material controls how surfaces respond to that light. The
80
82
  applies to AppIcons, ordinary controls, persistent chrome, raised cards, and every transient overlay: Dropdown, ContextMenu,
81
83
  MenuButton, AccountMenu, AppRail flyouts, Select, TagPicker, date pickers, tooltips, column menus,
82
84
  and toasts use `--zx-color-overlay-surface`; Launcher, Modal, Dialog, and overlay Sheets use the
83
- more opaque `--zx-color-overlay-panel`. A docked Sheet is layout rather than
84
- an overlay and becomes opaque, as do dense data surfaces. Glass and Deep glass progressively raise
85
- translucency, blur, highlights, and elevation; reduced-transparency preferences restore solid
86
- surfaces.
85
+ calmer `--zx-color-overlay-panel`. Both tiers combine a translucent base, responsive specular and
86
+ Aurora tint layers, backdrop filtering, and dedicated state/divider roles; the modal backdrop is a
87
+ separate material token so it does not erase the light the panel should refract. A docked Sheet is
88
+ layout rather than an overlay and becomes opaque, as do dense data surfaces. Glass and Deep glass
89
+ progressively raise translucency, blur, highlights, and elevation; reduced-transparency,
90
+ increased-contrast, forced-colour, and unsupported-filter environments restore solid surfaces.
91
+ Top-layer components remain DOM children of their nearest Zx theme scope, so a theme or material
92
+ set on an application region continues to inherit through its menus, dialogs, tooltips, and toasts.
87
93
 
88
94
  Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
89
95
  `--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-glass-chrome/surface`,
90
- `--zx-color-overlay-surface/panel/border`, `--zx-color-border(-strong/-control)`,
96
+ `--zx-color-overlay-surface/panel/backdrop/border/hover/selected/divider/scrim`,
97
+ `--zx-overlay-surface-image/toast-image/panel-image`, `--zx-color-border(-strong/-control)`,
91
98
  `--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
92
99
  `--zx-color-danger/warning/success/info(+ -bg)`, `--zx-focus-ring`, `--zx-control-height/-radius`,
93
100
  `--zx-space-*`, `--zx-radius-*`, `--zx-text-*`. Never use tier-1 palette tokens (`--zx-gray-*`,
@@ -248,28 +255,6 @@ reflection and ElementInternals form association: `<zx-toggle>`, `<zx-check-butt
248
255
  `<zx-checklist>`, `<zx-datebox>`, `<zx-timebox>`, `<zx-search>`, `<zx-groupbox>`, `<zx-tabbox>`,
249
256
  `<zx-table>`, `<zx-dialog>`.
250
257
 
251
- ## Conventions (editing component source)
252
-
253
- Follow `AGENTS.md`. Key rules: never declare instance class fields for state used in `render()`
254
- (render runs inside the base constructor before field initializers); route all DOM listeners
255
- through `this.listen()`; no `innerHTML` outside `h.raw()`; express state via ARIA/`data-*`, not
256
- state classes; small glyph buttons use the shared `.zx-icon-btn` utility; follow the named APG
257
- pattern with `:focus-visible` rings and `prefers-reduced-motion` guards.
258
-
259
- ## File map & commands
260
-
261
- ```
262
- src/components/<name>/ component + CSS src/core/ kernel (component, dom, http, i18n, date…)
263
- website/ marketing page + docs.html (docs.js) specs/ per-component specs
264
- website/demos/ per-component demo modules website/layouts/ application layout examples
265
- tests/ node unit + smoke dist/ built bundles
266
-
267
- npm run serve # http://127.0.0.1:8321/website/docs.html (no build)
268
- npm test # node --test tests/unit/*.test.js + node tests/lint-tokens.js
269
- npm run build # dist/: ESM, global, ZeyOS binding, and CSS assets
270
- node tools/build-module-tokens.js # regenerate styles/tokens/modules.css from src/zeyos/modules.js
271
- ```
272
-
273
258
  ---
274
259
 
275
260
  # Components
@@ -456,11 +441,13 @@ A native-dialog launcher for applications, recent records, commands, and grouped
456
441
  Zx owns ranking, focus, request cancellation, and presentation; ZeyOS owns the catalogue,
457
442
  permissions, recent-history/cache policy, and the route or command ultimately invoked.
458
443
 
459
- - **Constructor** — `new Launcher(null, options)` owns a body-level dialog;
444
+ - **Constructor** — `new Launcher(null, options)` owns a top-layer dialog in the opener's nearest
445
+ Zx theme scope;
460
446
  `new Launcher(existingDialog, options)` enhances and later restores an existing `<dialog>`.
461
447
  - **Options** — `items`, abortable `sources: [{id, label?, minQuery?, when?, order?, load(query, {signal})}]`,
462
448
  `query`, `debounce: 250`, `minQuery: 0`, `maxResults: 100`, `placeholder`, `label`, `emptyText`,
463
- `loadingText`, `shortcut: 'mod+k'|false`, and visible `hints` labels (or `false`).
449
+ `loadingText`, `shortcut: 'mod+k'|false`, visible `hints` labels (or `false`), and `scope` to
450
+ explicitly select an element whose nearest Zx theme scope owns the launcher.
464
451
  - **Items** — `{id, label, description?, keywords?, group?, icon?, badge?, value?, href?, target?,
465
452
  invoke?, pinned?, disabled?, kind?, current?, when?, groupOrder?, itemOrder?}`. `icon` accepts a
466
453
  bundled icon name, Node, or factory. Links stay native so modified and middle clicks work;
@@ -787,8 +774,10 @@ The shared floating region is fixed to the upper-right logical corner by default
787
774
  messages rather than the page, and uses the translucent glass surface tokens with a solid fallback.
788
775
 
789
776
  - **Statics** — `Message.info/success/warning/error(msg, opts)`,
790
- `Message.show(msg, { kind, timeout=4000, closable })` → `{ close() }`,
791
- `Message.progress(text)` → `{ update(pct, text?), done(), fail(text?) }`.
777
+ `Message.show(msg, { kind, timeout=4000, closable, scope? })` → `{ close() }`,
778
+ `Message.progress(text, {scope?})` → `{ update(pct, text?), done(), fail(text?) }`. `scope` is
779
+ an element or selector whose nearest Zx theme scope owns that floating region; without it the
780
+ focused opener supplies the scope.
792
781
  - **Per-message options** — `kind: 'info'`, `timeout`, `closable: true`, `maxVisible: 5` (extras
793
782
  queue, and hovering pauses dismissal).
794
783
  <!-- /doc -->
@@ -797,10 +786,10 @@ messages rather than the page, and uses the translucent glass surface tokens wit
797
786
  ### Modal
798
787
 
799
788
  Thin overlay on the native `<dialog>` element. The surface uses the shared, reading-sized overlay
800
- panel material; the backdrop uses `--zx-color-bg-backdrop`, and focus returns to the opener on close.
789
+ panel material; the backdrop uses `--zx-color-overlay-backdrop`, and focus returns to the opener on close.
801
790
 
802
791
  - **Options** — `content`, `width: 'auto'`, `closable: true`, `lightDismiss: false`,
803
- `destroyOnClose: false`.
792
+ `destroyOnClose: false`, `scope: null` (the opener's nearest Zx theme scope by default).
804
793
  - **Methods** — `open()`, `close(result?)`, `setContent()`, `isOpen()`.
805
794
  - **Events** — `open`, `close {result}`, `cancel` (Esc; preventable with `event.preventDefault()`).
806
795
  <!-- /doc -->
@@ -832,7 +821,7 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
832
821
  (`true | 'trap-focus' | false`), `backdrop: 'dim'` (`'dim' | 'blur' | 'none'`), `size: null`
833
822
  (CSS length or px number, applied to whichever axis `side` implies), `closeButton: true`,
834
823
  plus everything Dialog takes — `title`, `content`, `buttons`, `closable`, `lightDismiss`
835
- (defaults to `true` here, unlike Modal), `destroyOnClose`.
824
+ (defaults to `true` here, unlike Modal), `destroyOnClose`, `scope`.
836
825
  - **Methods** — `open()`, `close(result?)`, `setSide(side)`, `getSide()`, `setSize(size)`,
837
826
  `isModal()`, `isDocked()`, `isOpen()`, `getSize()`, `snapTo(target)`, plus Dialog's `setTitle()`, `setContent()`,
838
827
  `setButtons()`, `addView()`, `showView()`.
@@ -846,10 +835,12 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
846
835
  across a handoff: the element moves and reopens in place, so DOM state and listeners survive,
847
836
  and no `open`/`close` is emitted for what is only a change of address.
848
837
  - **Modality** — `true` delegates focus containment, page inertness, Escape, and the backdrop to
849
- the browser through `showModal()`. `'trap-focus'` and `false` open with `show()`, so the page
850
- keeps scrolling and stays interactive; Escape and outside-click are re-implemented, and
851
- `'trap-focus'` adds `focusTrap()` on top. Only a modal sheet renders a `::backdrop`, so
852
- `backdrop` is meaningful only when `modal` is `true`.
838
+ the browser through `showModal()`. Floating `'trap-focus'` and `false` sheets use a manual
839
+ Popover top layer, so they stay viewport-anchored even inside a transformed or clipped theme
840
+ scope while the page keeps scrolling and stays interactive. Escape and outside-click are
841
+ re-implemented, and `'trap-focus'` adds `focusTrap()` on top. Their top-layer backdrop is
842
+ explicitly transparent and non-filtering; `backdrop` is meaningful only when `modal` is `true`.
843
+ A docked Sheet remains a native non-modal dialog in its Dock's flow.
853
844
  - **Resizing and detents** — `resizable` makes the inner edge draggable between `min` and `max`.
854
845
  `snap` gives it detents: a number at or below 1 is a fraction of the viewport along the sheet's
855
846
  axis, anything larger is pixels, and a string is any CSS length (`'320px'`, `'90%'`). With
@@ -920,12 +911,14 @@ alone, `new SheetStack({…})`.
920
911
  ### Dropdown
921
912
 
922
913
  Generic anchored popover in the top layer, constructed as `new Dropdown(anchor, options)`. It
923
- light-dismisses on an outside click or Esc, and mirrors `aria-expanded` onto the anchor.
914
+ light-dismisses on an outside click or Esc, mirrors `aria-expanded` onto the anchor, and stays a
915
+ DOM child of the anchor's nearest Zx theme scope while the browser lifts it into the top layer.
924
916
 
925
917
  - **Options** — `content`,
926
918
  `placement: 'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'` plus the matching
927
919
  `left*` and `right*` placements, `offset: 4`,
928
- `matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false`.
920
+ `matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false`, `scope: null` (the
921
+ anchor's nearest Zx theme scope by default).
929
922
  - **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setContent()`, `getPanel()`.
930
923
  - **Events** — `open`, `close`.
931
924
  <!-- /doc -->
@@ -1138,8 +1131,15 @@ portable state as TableView and KanbanView.
1138
1131
  `previewAlt`, `link`, `actions`, `groupBy`, `groupOrder`, `minCardWidth`, `maxColumns`,
1139
1132
  `variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, and `label`.
1140
1133
  - **Cards** — each record is a focusable `<li>` with a native title link, separate secondary
1141
- actions, optional native multi-select checkbox, labelled metadata, and `aria-selected` state.
1142
- Title/subtitle fields are not duplicated in metadata unless their field sets `duplicate:true`.
1134
+ actions, optional native multi-select checkbox, labelled metadata, and a global ARIA selection
1135
+ description that retains valid listitem semantics.
1136
+ CardView uses a dense collection rhythm: titles clamp to two lines, previews stay at
1137
+ thumbnail/avatar scale, and visible fields become compact labelled metadata chips. Title/subtitle
1138
+ fields are not duplicated in metadata unless their field sets `duplicate:true`.
1139
+ - **Shared card slots** — a visible field may set
1140
+ `view.card.slot: 'eyebrow-start'|'eyebrow-end'|'title-prefix'`. These slots create a semantic
1141
+ eyebrow row or put labelled content immediately before (and outside) the title heading/link.
1142
+ Unknown slots remain ordinary metadata; visibility and field order still control rendering.
1143
1143
  - **Media and URLs** — `preview` accepts a field ID or callback returning an image URL,
1144
1144
  `{src,alt,fit}`, Node, or null. Executable/non-image schemes are rejected and image failures show
1145
1145
  a stable fallback. Title/action links are normalized; `_blank` links receive `noopener`.
@@ -1163,14 +1163,25 @@ cards, advisory work-in-progress limits, and equivalent pointer/programmatic/key
1163
1163
  columns may also declare an advisory `limit`.
1164
1164
  - **Methods** — configure/get/move columns and swim lanes, collapse either axis, and
1165
1165
  `moveRecord(id,{column?,lane?,index?})`. Board-specific order/collapse joins the common view
1166
- state; record order, movement history, and selection do not.
1166
+ state; record order, movement history, and selection do not. Saved preferences for data-derived
1167
+ axes survive an empty/partial result set and reconcile as later records introduce their IDs.
1168
+ - **Dense board layout** — bounded muted columns use a stable 20rem rail with native horizontal
1169
+ overflow, 48px headings, compact counts, 8–12px card padding, and 8px card gaps. Surface cards
1170
+ use an optional square thumbnail and a two-tier hierarchy from shared card field slots:
1171
+ `eyebrow-start`, `eyebrow-end`, and `title-prefix`. Unslotted metadata stays as quiet inline
1172
+ facts. Titles clamp visually to two lines while retaining their complete accessible text. Move
1173
+ and action controls reveal on hover/focus/selection/grab, and remain visible on touch devices.
1167
1174
  - **Movement boundary** — `recordmove {record,id,from,to,column,swimlane,limitExceeded}` is
1168
- cancelable and fires before a commit/request. `accept` is a hard eligibility gate; WIP is visual
1169
- advice. Local mode clones the moved record and never mutates host data. External mode emits only,
1170
- for server validation and optimistic rollback owned by ZeyOS.
1175
+ cancelable and fires before a commit/request; either the component event or bubbling
1176
+ `zx-recordmove` mirror may veto it. `accept` is a hard eligibility gate; WIP is visual advice.
1177
+ Local mode clones the moved record and never mutates host data, and a manual local move clears an
1178
+ active local sort so saved state matches visible order. External mode emits only, for server
1179
+ validation and optimistic rollback owned by ZeyOS.
1171
1180
  - **Keyboard** — focus the move handle; Enter/Space grabs or drops, Escape cancels, Left/Right
1172
1181
  chooses a column, Up/Down chooses a bucket-relative position, and Alt+Up/Down chooses a swim
1173
- lane. A polite live region announces every target and result. Native drag is an enhancement.
1182
+ lane. A polite live region announces every target, result, and reached/exceeded WIP limit.
1183
+ Collapse keeps focus on its control, including when a programmatic collapse hides the focused
1184
+ card. Native drag is an enhancement.
1174
1185
  <!-- /doc -->
1175
1186
 
1176
1187
  <!-- doc:grid -->
@@ -1243,7 +1254,9 @@ windowed loader.
1243
1254
  HTML, and suspicious CSS colour strings are ignored.
1244
1255
  - **Methods** — `getEvents()`, `setEvents()`, `addEvent()`, `updateEvent()`, `removeEvent()`,
1245
1256
  `getDate()`, `setDate()`, `getView()`, `setView()`, `prev()`, `next()`, `today()`,
1246
- `setLoading()`, `focusEvent()`, `destroy()`.
1257
+ `setLoading()`, `focusEvent()`, `destroy()`. `prev()`/`next()` step one visible period and clamp
1258
+ the day of month into the target month, so a 29th–31st anchor cannot skip a shorter month; the
1259
+ same arithmetic is exported as `calendarPageDate()` and `addCalendarMonths()`.
1247
1260
  - **Events** — `dateschange {view,start,end}`, `viewchange {view,oldView}`, `eventclick`,
1248
1261
  `eventdblclick`, `dateclick {date,allDay,view}`, `select {start,end,allDay,view}`,
1249
1262
  `new {date,view}`, `moreclick {date,count}`, `eventschange {events}`, and cancelable
@@ -1252,6 +1265,12 @@ windowed loader.
1252
1265
  idempotent, version-guarded `detail.revert()` if persistence fails. With `optimistic: false`, the
1253
1266
  component stays controlled: it emits the proposal without changing data, and the application
1254
1267
  applies an accepted response through `updateEvent()` or `setEvents()`. Zx does not send requests.
1268
+ - **View layout** — agenda days keep every event in one vertically stacked content column. Month
1269
+ uses a continuous seven-day grid with full-height day boundaries and event lanes overlaid below
1270
+ the date labels; today is indicated with accent text instead of a decorative edge highlight.
1271
+ - **Pointer editing** — a timed move preserves the point where the event was grabbed and snaps the
1272
+ resulting start to `slotDuration`. The lower grip stretches the event in its original day column,
1273
+ while pointer capture keeps either interaction active if the pointer leaves the event surface.
1255
1274
  - **Keyboard** — time slots and date grids use arrow-key navigation, Home/End, PageUp/PageDown,
1256
1275
  Enter/Space activation, and roving focus. On an editable event, Space grabs it, arrows propose a
1257
1276
  slot/day move (or change the focused resize grip), Space drops, and Escape cancels. Live status
package/docs/llms.txt CHANGED
@@ -16,9 +16,7 @@ exports and as `window.zx.*`.
16
16
  - [Documentation site](https://zx.zeyos.com/docs.html): getting started, every component with a live demo and its JavaScript source, and the application layouts. Machine index: [llms.txt](https://zx.zeyos.com/llms.txt).
17
17
  - [README](../README.md): what Zx is, quick start (ESM + global script), component index, theming, and browser support.
18
18
  - [API reference](llms.md): per-component constructor options, methods, and events for all public components plus the core helpers.
19
- - [Skill](https://zx.zeyos.com/skills/zx/SKILL.md): task-oriented guide — mental model, recipes, ZeyOS HTTP client, theming, gotchas, commands.
20
19
  - [Changelog](../CHANGELOG.md): what changed in each release, including behaviour changes an upgrade has to account for.
21
- - [Contributor contract](https://github.com/zeyos/zx/blob/main/AGENTS.md): conventions for editing component source (token rules, render-time state, listeners, ARIA/APG).
22
20
 
23
21
  ## Components
24
22
  - Inputs: `button()`, `buttonGroup()`, `badge()`/`badgeGroup()`, `CheckButton`,
@@ -52,7 +50,7 @@ exports and as `window.zx.*`.
52
50
  - Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
53
51
 
54
52
  ## Theming
55
- Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share a floating overlay material; Launcher, Modal, Dialog, and overlay Sheets share a more opaque panel material. Docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
53
+ Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share responsive compact glass; Launcher, Modal, Dialog, and floating Sheets share a calmer reading panel. Both preserve scoped theme inheritance and refract Aurora/page content; docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
56
54
 
57
55
  ## Talking to ZeyOS
58
56
  Use the dedicated ZeyOS client library **`@zeyos/client`** (`npm install @zeyos/client`) for ZeyOS business data: `createZeyosClient({ platform, auth })`, typed `client.api.list/get/create/update<Resource>` over the full OpenAPI surface, and `normalizeListResult`. Zx accepts the client by injection and does not bundle it. See the `website/layouts/zeyos-invoices.layout.js` layout for a Zx-components-on-@zeyos/client reference. Zx also ships a minimal `Http`/`zeyosService` for ad-hoc in-app `remotecall`, but `@zeyos/client` is the recommended default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/zx",
3
- "version": "4.2.0",
3
+ "version": "4.3.4",
4
4
  "description": "Dependency-free JavaScript implementation of the Xenon Design System.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -65,7 +65,6 @@
65
65
  "docs/api.json",
66
66
  "CHANGELOG.md",
67
67
  "README.md",
68
- "DESIGN-SYSTEM.md",
69
68
  "LICENSE"
70
69
  ],
71
70
  "engines": {
@@ -60,7 +60,7 @@
60
60
  min-inline-size: 220px;
61
61
  gap: 2px;
62
62
  margin: calc(-1 * var(--zx-space-1)) calc(-1 * var(--zx-space-1)) var(--zx-space-1);
63
- border-block-end: 1px solid var(--zx-color-border);
63
+ border-block-end: 1px solid var(--zx-color-overlay-divider);
64
64
  padding: var(--zx-space-3);
65
65
  }
66
66
 
@@ -101,7 +101,7 @@
101
101
  }
102
102
  .zx-app-rail__flyout-item:hover,
103
103
  .zx-app-rail__flyout-item:focus-visible,
104
- .zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-bg-hover); color: var(--zx-color-accent); }
104
+ .zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-overlay-hover); color: var(--zx-color-accent); }
105
105
  .zx-app-rail__flyout-item-icon { display: inline-flex; grid-column: 1; }
106
106
  .zx-app-rail__flyout-item-label { grid-column: 2; }
107
107
  .zx-app-rail__flyout-item-badge { grid-column: 3; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
@@ -70,6 +70,51 @@ export function calendarRange(view, anchor, options = {}) {
70
70
  return { start, end };
71
71
  }
72
72
 
73
+ /**
74
+ * Returns the anchor date one visible period away from `anchor`.
75
+ *
76
+ * Month and year steps clamp the day of month into the target month instead of letting it
77
+ * overflow: `setMonth()` alone turns 31 January into 3 March, which makes February unreachable
78
+ * from a month-end anchor and leaves `prev()` on 31 March landing back inside March.
79
+ * @param {CalendarView} view Current view.
80
+ * @param {Date} anchor Current anchor date.
81
+ * @param {number} direction -1 for the previous period, 1 for the next.
82
+ * @param {{agendaDays?: number}} [options={}] Paging options.
83
+ * @returns {Date}
84
+ */
85
+ export function calendarPageDate(view, anchor, direction, options = {}) {
86
+ const date = validDate(anchor, 'Calendar date');
87
+ const step = integer(direction, 'direction');
88
+ if (view === 'day') date.setDate(date.getDate() + step);
89
+ else if (view === 'agenda') {
90
+ date.setDate(date.getDate() + step * positiveInteger(options.agendaDays ?? 14, 'agendaDays'));
91
+ } else if (view === 'week') date.setDate(date.getDate() + step * 7);
92
+ else if (view === 'month') return addCalendarMonths(date, step);
93
+ else if (view === 'year') return addCalendarMonths(date, step * 12);
94
+ else throw new RangeError(`Unknown calendar view: ${view}`);
95
+ return date;
96
+ }
97
+
98
+ /**
99
+ * Adds whole months, clamping the day of month to the last day the target month actually has.
100
+ * @param {Date} value Local date.
101
+ * @param {number} months Months to add.
102
+ * @returns {Date}
103
+ */
104
+ export function addCalendarMonths(value, months) {
105
+ const date = validDate(value, 'Calendar date');
106
+ const day = date.getDate();
107
+ date.setDate(1);
108
+ date.setMonth(date.getMonth() + integer(months, 'month amount'));
109
+ date.setDate(Math.min(day, daysInCalendarMonth(date.getFullYear(), date.getMonth())));
110
+ return date;
111
+ }
112
+
113
+ /** @param {number} year @param {number} month @returns {number} */
114
+ function daysInCalendarMonth(year, month) {
115
+ return new Date(year, month + 1, 0).getDate();
116
+ }
117
+
73
118
  /**
74
119
  * Normalizes caller records atomically into the Calendar event contract.
75
120
  * @param {Record<string, any>[]} records Source records.