@zeyos/zx 4.0.0 → 4.1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/DESIGN-SYSTEM.md +59 -3
  3. package/dist/types/components/app-icon/app-icon.d.ts +88 -0
  4. package/dist/types/components/filter/filter-model.d.ts +87 -0
  5. package/dist/types/components/filter/filter.d.ts +331 -0
  6. package/dist/types/components/select/select.d.ts +46 -4
  7. package/dist/types/components/table/table.d.ts +103 -0
  8. package/dist/types/components/tag-picker/tag-picker.d.ts +16 -0
  9. package/dist/types/core/dom.d.ts +1 -1
  10. package/dist/types/core/util.d.ts +9 -0
  11. package/dist/types/index.d.ts +4 -1
  12. package/dist/types/zeyos/icons.d.ts +19 -2
  13. package/dist/types/zeyos/index.d.ts +1 -1
  14. package/dist/types/zeyos/select.d.ts +2 -0
  15. package/dist/zx-zeyos.esm.js +702 -75
  16. package/dist/zx-zeyos.esm.min.js +1 -1
  17. package/dist/zx.css +415 -4
  18. package/dist/zx.esm.js +1568 -115
  19. package/dist/zx.esm.min.js +3 -3
  20. package/dist/zx.global.js +1568 -115
  21. package/dist/zx.global.min.js +3 -3
  22. package/dist/zx.min.css +1 -1
  23. package/docs/api.json +379 -0
  24. package/docs/llms.md +93 -16
  25. package/docs/llms.txt +11 -9
  26. package/package.json +1 -1
  27. package/src/components/app-icon/app-icon.css +105 -0
  28. package/src/components/app-icon/app-icon.js +134 -0
  29. package/src/components/filter/filter-model.js +226 -0
  30. package/src/components/filter/filter.css +147 -0
  31. package/src/components/filter/filter.js +554 -0
  32. package/src/components/grid/grid.css +1 -1
  33. package/src/components/grid/grid.js +4 -1
  34. package/src/components/message/message.css +36 -3
  35. package/src/components/select/select.css +17 -0
  36. package/src/components/select/select.js +105 -24
  37. package/src/components/table/table.css +85 -0
  38. package/src/components/table/table.js +418 -24
  39. package/src/components/tag-picker/tag-picker.css +19 -0
  40. package/src/components/tag-picker/tag-picker.js +42 -5
  41. package/src/core/dom.js +1 -1
  42. package/src/core/util.js +20 -0
  43. package/src/index.js +7 -1
  44. package/src/zeyos/icons.js +34 -21
  45. package/src/zeyos/index.js +1 -1
  46. package/src/zeyos/select.js +6 -5
  47. package/styles/tokens/semantic.css +9 -0
  48. package/styles/tokens/themes.css +2 -2
  49. package/styles/zx.css +2 -0
package/docs/api.json CHANGED
@@ -130,6 +130,79 @@
130
130
  "events": [],
131
131
  "methods": []
132
132
  },
133
+ "AppIcon": {
134
+ "options": [
135
+ {
136
+ "name": "icon",
137
+ "type": "string|Node|(()=>string|Node)|null",
138
+ "default": "null",
139
+ "description": "Icon name, node, or lazy renderer."
140
+ },
141
+ {
142
+ "name": "color",
143
+ "type": "string|null",
144
+ "default": "null",
145
+ "description": "Identity colour; falls back to the accent colour."
146
+ },
147
+ {
148
+ "name": "size",
149
+ "type": "number|string",
150
+ "default": "36",
151
+ "description": "Edge length in pixels or any CSS length."
152
+ },
153
+ {
154
+ "name": "iconSize",
155
+ "type": "number|string",
156
+ "default": "'52%'",
157
+ "description": "Glyph size."
158
+ },
159
+ {
160
+ "name": "label",
161
+ "type": "string|null",
162
+ "default": "null",
163
+ "description": "Accessible image label; null makes the icon decorative."
164
+ },
165
+ {
166
+ "name": "badge",
167
+ "type": "string|number|null",
168
+ "default": "null",
169
+ "description": "Optional corner badge."
170
+ },
171
+ {
172
+ "name": "selected",
173
+ "type": "boolean",
174
+ "default": "false",
175
+ "description": "Whether the icon represents the active application."
176
+ },
177
+ {
178
+ "name": "glass",
179
+ "type": "false|'subtle'|'strong'",
180
+ "default": "'subtle'",
181
+ "description": "CSS-first glass treatment."
182
+ },
183
+ {
184
+ "name": "class",
185
+ "type": "string|string[]",
186
+ "default": null,
187
+ "description": "Additional class names."
188
+ }
189
+ ],
190
+ "events": [],
191
+ "methods": [
192
+ {
193
+ "name": "set",
194
+ "signature": "set(values)",
195
+ "returns": "this",
196
+ "description": "Updates identity or presentation without replacing the root."
197
+ },
198
+ {
199
+ "name": "destroy",
200
+ "signature": "destroy()",
201
+ "returns": "void",
202
+ "description": "Restores an enhanced target."
203
+ }
204
+ ]
205
+ },
133
206
  "AppSidebar": {
134
207
  "options": [
135
208
  {
@@ -2456,6 +2529,54 @@
2456
2529
  "events": [],
2457
2530
  "methods": []
2458
2531
  },
2532
+ "EntitySelect": {
2533
+ "options": [
2534
+ {
2535
+ "name": "recent",
2536
+ "type": "SelectItem[]",
2537
+ "default": "[]",
2538
+ "description": "Choices pinned above ordinary results."
2539
+ },
2540
+ {
2541
+ "name": "recentLabel",
2542
+ "type": "string",
2543
+ "default": "'Recent'",
2544
+ "description": "Heading above recent choices."
2545
+ },
2546
+ {
2547
+ "name": "create",
2548
+ "type": "boolean|SelectAction",
2549
+ "default": "false",
2550
+ "description": "Optional create command at the bottom."
2551
+ },
2552
+ {
2553
+ "name": "iconKey",
2554
+ "type": "string|((item: SelectItem)=>unknown)",
2555
+ "default": "'icon'",
2556
+ "description": "Item icon reader."
2557
+ },
2558
+ {
2559
+ "name": "colorKey",
2560
+ "type": "string|((item: SelectItem)=>unknown)",
2561
+ "default": "'color'",
2562
+ "description": "Optional icon colour reader."
2563
+ },
2564
+ {
2565
+ "name": "subtitleKey",
2566
+ "type": "string|((item: SelectItem)=>unknown)",
2567
+ "default": "'description'",
2568
+ "description": "Secondary-text reader."
2569
+ },
2570
+ {
2571
+ "name": "renderIcon",
2572
+ "type": "((item: SelectItem, context:{selected:boolean})=>Node|string|null)|null",
2573
+ "default": "null",
2574
+ "description": "Entity icon renderer."
2575
+ }
2576
+ ],
2577
+ "events": [],
2578
+ "methods": []
2579
+ },
2459
2580
  "Field": {
2460
2581
  "options": [
2461
2582
  {
@@ -2832,6 +2953,176 @@
2832
2953
  }
2833
2954
  ]
2834
2955
  },
2956
+ "Filter": {
2957
+ "options": [
2958
+ {
2959
+ "name": "fields",
2960
+ "type": "FilterField[]",
2961
+ "default": null,
2962
+ "description": "Field definitions."
2963
+ },
2964
+ {
2965
+ "name": "value",
2966
+ "type": "FilterAst",
2967
+ "default": null,
2968
+ "description": "Initial expression."
2969
+ },
2970
+ {
2971
+ "name": "operators",
2972
+ "type": "Array<{id:string,label:string,arity:'none'|'single'|'pair'|'many',types:string[]}>",
2973
+ "default": null,
2974
+ "description": "Additional/overridden operators."
2975
+ },
2976
+ {
2977
+ "name": "allowGroups",
2978
+ "type": "boolean",
2979
+ "default": "true",
2980
+ "description": "Whether nested groups may be authored."
2981
+ },
2982
+ {
2983
+ "name": "maxDepth",
2984
+ "type": "number",
2985
+ "default": "3",
2986
+ "description": "Maximum nested group depth."
2987
+ },
2988
+ {
2989
+ "name": "maxConditions",
2990
+ "type": "number",
2991
+ "default": "50",
2992
+ "description": "Maximum condition count."
2993
+ },
2994
+ {
2995
+ "name": "autoApply",
2996
+ "type": "boolean",
2997
+ "default": "false",
2998
+ "description": "Emit apply after every valid mutation."
2999
+ },
3000
+ {
3001
+ "name": "readonly",
3002
+ "type": "boolean",
3003
+ "default": "false",
3004
+ "description": "Prevent mutations while keeping values readable."
3005
+ },
3006
+ {
3007
+ "name": "disabled",
3008
+ "type": "boolean",
3009
+ "default": "false",
3010
+ "description": "Disable all controls."
3011
+ },
3012
+ {
3013
+ "name": "applyLabel",
3014
+ "type": "string",
3015
+ "default": "'Apply filters'",
3016
+ "description": "Apply button label."
3017
+ },
3018
+ {
3019
+ "name": "clearLabel",
3020
+ "type": "string",
3021
+ "default": "'Clear'",
3022
+ "description": "Clear button label."
3023
+ }
3024
+ ],
3025
+ "events": [
3026
+ {
3027
+ "name": "change",
3028
+ "payload": "Record<string,unknown>",
3029
+ "description": "Draft-change listener."
3030
+ },
3031
+ {
3032
+ "name": "apply",
3033
+ "payload": "Record<string,unknown>",
3034
+ "description": "Valid-apply listener."
3035
+ },
3036
+ {
3037
+ "name": "invalid",
3038
+ "payload": "Record<string,unknown>",
3039
+ "description": "Invalid-apply listener."
3040
+ },
3041
+ {
3042
+ "name": "query",
3043
+ "payload": "Record<string,unknown>",
3044
+ "description": "Async query listener."
3045
+ },
3046
+ {
3047
+ "name": "loaded",
3048
+ "payload": "Record<string,unknown>",
3049
+ "description": "Async loaded listener."
3050
+ },
3051
+ {
3052
+ "name": "error",
3053
+ "payload": "Record<string,unknown>",
3054
+ "description": "Async failure listener."
3055
+ }
3056
+ ],
3057
+ "methods": [
3058
+ {
3059
+ "name": "getValue",
3060
+ "signature": "getValue()",
3061
+ "returns": "FilterAst",
3062
+ "description": "Returns a defensive AST copy."
3063
+ },
3064
+ {
3065
+ "name": "setValue",
3066
+ "signature": "setValue(value, [options])",
3067
+ "returns": "this",
3068
+ "description": "Atomically replaces the AST."
3069
+ },
3070
+ {
3071
+ "name": "addCondition",
3072
+ "signature": "addCondition([parentId], [initial])",
3073
+ "returns": "string|null",
3074
+ "description": "Adds a condition to a group."
3075
+ },
3076
+ {
3077
+ "name": "addGroup",
3078
+ "signature": "addGroup([parentId], [logic])",
3079
+ "returns": "string|null",
3080
+ "description": "Adds a nested group."
3081
+ },
3082
+ {
3083
+ "name": "update",
3084
+ "signature": "update(id, patch)",
3085
+ "returns": "this",
3086
+ "description": "Updates a node by stable ID."
3087
+ },
3088
+ {
3089
+ "name": "remove",
3090
+ "signature": "remove(id)",
3091
+ "returns": "this",
3092
+ "description": "Removes a non-root node."
3093
+ },
3094
+ {
3095
+ "name": "move",
3096
+ "signature": "move(id, parentId, index)",
3097
+ "returns": "this",
3098
+ "description": "Moves a node into a group while preserving its identity."
3099
+ },
3100
+ {
3101
+ "name": "validate",
3102
+ "signature": "validate()",
3103
+ "returns": "{valid:boolean,errors:Array<Record<string,string>>}",
3104
+ "description": "Returns semantic validity and errors."
3105
+ },
3106
+ {
3107
+ "name": "apply",
3108
+ "signature": "apply()",
3109
+ "returns": "FilterAst|null",
3110
+ "description": "Applies only a valid AST."
3111
+ },
3112
+ {
3113
+ "name": "clear",
3114
+ "signature": "clear([options])",
3115
+ "returns": "this",
3116
+ "description": "Clears all conditions."
3117
+ },
3118
+ {
3119
+ "name": "focus",
3120
+ "signature": "focus([id])",
3121
+ "returns": "this",
3122
+ "description": "Focuses a condition's field or the Add button."
3123
+ }
3124
+ ]
3125
+ },
2835
3126
  "Finder": {
2836
3127
  "options": [
2837
3128
  {
@@ -5025,6 +5316,12 @@
5025
5316
  "default": "[]",
5026
5317
  "description": "Choices pinned above the list, kept through filtering and async loads."
5027
5318
  },
5319
+ {
5320
+ "name": "fixedLabel",
5321
+ "type": "string|null",
5322
+ "default": "null",
5323
+ "description": "Optional heading above pinned choices."
5324
+ },
5028
5325
  {
5029
5326
  "name": "valueKey",
5030
5327
  "type": "SelectValueReader",
@@ -5245,6 +5542,12 @@
5245
5542
  "returns": "Select",
5246
5543
  "description": "Creates a status picker with shape- and color-distinct workflow indicators."
5247
5544
  },
5545
+ {
5546
+ "name": "entity",
5547
+ "signature": "entity(target, [options])",
5548
+ "returns": "Select",
5549
+ "description": "Creates a rich entity picker with optional Recent choices and a create command."
5550
+ },
5248
5551
  {
5249
5552
  "name": "permission",
5250
5553
  "signature": "permission(target, [options])",
@@ -6379,6 +6682,30 @@
6379
6682
  "default": "false",
6380
6683
  "description": "Inline editing mode. `'cell'` edits one cell, `'row'` opens every editable cell of the row together and commits them as a unit. Editing is completely inert while this is `false`."
6381
6684
  },
6685
+ {
6686
+ "name": "editTrigger",
6687
+ "type": "'single'|'double'",
6688
+ "default": "'double'",
6689
+ "description": "Gesture that starts an editable cell."
6690
+ },
6691
+ {
6692
+ "name": "rowReorder",
6693
+ "type": "boolean",
6694
+ "default": "false",
6695
+ "description": "Whether rows expose pointer/keyboard reorder handles."
6696
+ },
6697
+ {
6698
+ "name": "columnVisibility",
6699
+ "type": "boolean",
6700
+ "default": "false",
6701
+ "description": "Whether a column visibility chooser is rendered."
6702
+ },
6703
+ {
6704
+ "name": "hiddenColumns",
6705
+ "type": "string[]",
6706
+ "default": "[]",
6707
+ "description": "Initially hidden column IDs; at least one stays visible."
6708
+ },
6382
6709
  {
6383
6710
  "name": "hierarchy",
6384
6711
  "type": "false|TableHierarchyOptions",
@@ -6446,6 +6773,16 @@
6446
6773
  "name": "rowtoggle",
6447
6774
  "payload": "{row, id, expanded}",
6448
6775
  "description": ""
6776
+ },
6777
+ {
6778
+ "name": "rowmove",
6779
+ "payload": "{row, id, target, targetId, position}",
6780
+ "description": ""
6781
+ },
6782
+ {
6783
+ "name": "columnvisibilitychange",
6784
+ "payload": "{visible:string[],hidden:string[]}",
6785
+ "description": ""
6449
6786
  }
6450
6787
  ],
6451
6788
  "methods": [
@@ -6491,6 +6828,36 @@
6491
6828
  "returns": "TableRow[]",
6492
6829
  "description": "Returns a shallow copy of the current rows in display order."
6493
6830
  },
6831
+ {
6832
+ "name": "getVisibleColumns",
6833
+ "signature": "getVisibleColumns()",
6834
+ "returns": "string[]",
6835
+ "description": "Returns visible column IDs in caller order."
6836
+ },
6837
+ {
6838
+ "name": "getHiddenColumns",
6839
+ "signature": "getHiddenColumns()",
6840
+ "returns": "string[]",
6841
+ "description": "Returns hidden column IDs in caller order."
6842
+ },
6843
+ {
6844
+ "name": "setColumnVisible",
6845
+ "signature": "setColumnVisible(id, [visible], [options])",
6846
+ "returns": "this",
6847
+ "description": "Shows or hides one column. The final visible column cannot be hidden."
6848
+ },
6849
+ {
6850
+ "name": "toggleColumn",
6851
+ "signature": "toggleColumn(id, [options])",
6852
+ "returns": "this",
6853
+ "description": "Toggles one column."
6854
+ },
6855
+ {
6856
+ "name": "moveRow",
6857
+ "signature": "moveRow(id, targetId, [position])",
6858
+ "returns": "this",
6859
+ "description": "Reorders one row relative to another and emits a cancelable `rowmove` before committing. Hierarchical rows may only move among siblings; an active local sort disables manual order."
6860
+ },
6494
6861
  {
6495
6862
  "name": "empty",
6496
6863
  "signature": "empty()",
@@ -6669,6 +7036,18 @@
6669
7036
  "default": "'name'",
6670
7037
  "description": "Item label property or reader."
6671
7038
  },
7039
+ {
7040
+ "name": "iconKey",
7041
+ "type": "TagValueReader",
7042
+ "default": "'icon'",
7043
+ "description": "Optional item icon property or reader."
7044
+ },
7045
+ {
7046
+ "name": "colorKey",
7047
+ "type": "TagValueReader",
7048
+ "default": "'color'",
7049
+ "description": "Optional item colour property or reader."
7050
+ },
6672
7051
  {
6673
7052
  "name": "searchKeys",
6674
7053
  "type": "Array<TagValueReader>|null",
package/docs/llms.md CHANGED
@@ -73,6 +73,11 @@ renders every component family live and exports the CSS):
73
73
  Keep the 600 stop at 4.5:1 or better against white and the 400 stop at 4.5:1 against the dark page
74
74
  — that is what makes `--zx-color-on-accent` legible without being restated per theme.
75
75
 
76
+ The theme studio treats those stable IDs as complete recipes: selecting a preset also changes its
77
+ density, neutral tint, radius, control height, base text size, font stack, and material strength.
78
+ Its **Material** control is CSS-first (`Flat`, `Glass`, or `Deep glass`); only the deep setting uses
79
+ backdrop blur, and components fall back to solid surfaces under reduced-transparency preferences.
80
+
76
81
  Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
77
82
  `--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-border(-strong/-control)`,
78
83
  `--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
@@ -445,6 +450,30 @@ permissions, recent-history/cache policy, and the route or command ultimately in
445
450
  taking over application routing.
446
451
  <!-- /doc -->
447
452
 
453
+ <!-- doc:app-icon -->
454
+ ### AppIcon — application identity
455
+
456
+ A fixed-size, product-agnostic application tile. It keeps glyph geometry stable in launchers,
457
+ sidebars, entity results, and recent-item rows while identity colour and material remain explicit
458
+ options rather than application globals.
459
+
460
+ - **Options** — `icon` (bundled name, Node, or lazy renderer), `color`, `size: 36`,
461
+ `iconSize: '52%'`, `label`, `badge`, `selected: false`, `glass: false|'subtle'|'strong'`, and
462
+ additional `class` names.
463
+ - **Methods** — `set(partialOptions)` updates the existing root; `destroy()` restores an enhanced
464
+ target. `appIcon(options)` is the element-returning convenience factory.
465
+ - **ZeyOS preset** — import `zeyosAppIcon(module, options)` from `@zeyos/zx/zeyos`. Every current
466
+ ZeyOS application/module has a canonical colour and icon mapping in `src/zeyos/modules.js`;
467
+ entity names and aliases resolve through the same catalogue. `moduleChip()` is the compatible
468
+ alias. An application may call `useZeyosIcons()` for the existing custom glyph kit or inject an
469
+ offline `icon` override; Zx never loads a network asset automatically.
470
+ - **Material** — subtle glass is a CSS border/highlight treatment; strong glass progressively adds
471
+ `backdrop-filter`. `prefers-reduced-transparency` and browsers without support receive a solid
472
+ tile. The glyph and label semantics do not depend on the visual treatment.
473
+ - **Accessibility** — a non-empty `label` gives the root `role="img"`; otherwise it is decorative.
474
+ The badge is visual supplementary status, so include its meaning in `label` when it matters.
475
+ <!-- /doc -->
476
+
448
477
  <!-- doc:avatar -->
449
478
  ### Avatar
450
479
 
@@ -483,7 +512,7 @@ out, or mutates the session.
483
512
 
484
513
  Unifies single-select, local filtering, and async loading.
485
514
 
486
- - **Options** — `items: []`, `fixedItems: []`, `valueKey: 'ID'` (string key or `(item)=>id`),
515
+ - **Options** — `items: []`, `fixedItems: []`, optional `fixedLabel`, `valueKey: 'ID'` (string key or `(item)=>id`),
487
516
  `labelKey: 'name'` (string or `(item)=>string`), `renderItem`, `renderValue`,
488
517
  `renderValueAdornment`, `renderNone`, `noneLabel`, `actions`, `value`, `disabled`, `placeholder`,
489
518
  `clearable: false`, `filter: false | 'local' | async (query)=>items`, `searchKeys`,
@@ -504,11 +533,13 @@ Unifies single-select, local filtering, and async loading.
504
533
  and accepts replacement `items`; `Select.permission(target, {groups, value})` pins Private and
505
534
  Public above record-access groups. Its value is the tri-state ZeyOS stores (`false` private,
506
535
  `true` public, or a group ID), and `groups` takes an array or loader `(query)=>items`.
507
- - **ZeyOS entity preset** — import `zeyosEntitySelect(client, resource, options)` or the DOM-free
508
- `buildZeyosEntitySelectConfig()` from `@zeyos/zx/zeyos`. It composes the async client binding
509
- with module-colored icons, subtitle/group readers, a deliberate none row, and an optional
510
- application-owned create command. The selected value remains the record ID and exact product
511
- glyphs stay injectable through `renderIcon`.
536
+ - **Entity preset** — `Select.entity(target, options)` renders icon/color, label, and secondary
537
+ text using `iconKey`, `colorKey`, and `subtitleKey` (or `renderIcon`). `recent` pins a deduplicated
538
+ **Recent** section above ordinary/local/async results; `create: true|actionDescriptor` appends an
539
+ application-owned command at the bottom without changing the selected ID. `recentLabel` and the
540
+ ordinary Select options remain overridable. For schema-driven ZeyOS loading,
541
+ `zeyosEntitySelect(client, resource, options)` and `buildZeyosEntitySelectConfig()` compose this
542
+ preset from `@zeyos/zx/zeyos`; the core preset itself is backend-neutral.
512
543
  - **Keyboard** (APG combobox) — ArrowDown/Alt+ArrowDown open; arrows navigate and wrap; Home/End;
513
544
  Enter selects; Esc closes; Tab closes; printable characters filter (editable) or run a typeahead
514
545
  (readonly). `aria-activedescendant` tracks the active option.
@@ -707,6 +738,8 @@ use `TimePicker` or `DateTimeBox`.
707
738
 
708
739
  Floating toasts come from statics; `new Message(target)` gives an inline, non-floating area with
709
740
  the same `.show()`. The toast region is `role="status"`/`aria-live` (`role="alert"` for errors).
741
+ The shared floating region is fixed to the upper-right logical corner by default, sizes to its
742
+ messages rather than the page, and uses the translucent glass surface tokens with a solid fallback.
710
743
 
711
744
  - **Statics** — `Message.info/success/warning/error(msg, opts)`,
712
745
  `Message.show(msg, { kind, timeout=4000, closable })` → `{ close() }`,
@@ -947,7 +980,8 @@ range select.
947
980
  `toggleRow()`, `expandRow()`, `collapseRow()`, `expandAll()`, `collapseAll()`, and
948
981
  `getExpanded()`. The native disclosure button works by pointer and keyboard without selecting
949
982
  or clicking the row.
950
- - **Editing** — `editMode: false|'cell'|'row'` (default `false`, and completely inert until set).
983
+ - **Editing** — `editMode: false|'cell'|'row'` (default `false`, and completely inert until set),
984
+ plus `editTrigger: 'double'|'single'` (Table defaults to double; Grid defaults to single).
951
985
  `'cell'` edits one cell, `'row'` opens every editable cell of the row and commits them as a unit.
952
986
  Editors are sized into the cell without changing row height, and an editing cell carries
953
987
  `data-editing="true"` (`data-invalid="true"` plus an `aria-live` message when a validator rejects).
@@ -980,7 +1014,8 @@ range select.
980
1014
  commit with no changes emits `editcommit` with an empty `changes` map and leaves the data alone.
981
1015
  Sorting, filtering, or `setData()` while editing cancels the edit cleanly and emits `editcancel`.
982
1016
  - **Editing keyboard** — Enter or F2 on a focused editable cell starts editing (editable cells share
983
- one roving tab stop), double-click starts editing without emitting `rowdblclick`, Enter commits and
1017
+ one roving tab stop); the configured single/double pointer gesture starts it without emitting
1018
+ `rowdblclick`. Enter commits and
984
1019
  returns focus to the cell (Shift+Enter stays a newline in a `'textarea'`, Space toggles a
985
1020
  `'checkbox'`), Escape cancels and returns focus to the cell, Tab commits and moves to the next
986
1021
  editable cell (Shift+Tab the previous), wrapping across rows, and tabbing past the last editable
@@ -1006,6 +1041,14 @@ range select.
1006
1041
  - **Empty state** — `emptyText` takes a string, a Node, or a function returning one, so an empty
1007
1042
  table can carry an icon, an explanation, and the action that resolves it (`emptyState({…})`).
1008
1043
  A function is re-called on every render, which is what to use when the placeholder holds controls.
1044
+ - **Row order** — `rowReorder: true` adds a dedicated drag handle. Pointer drag moves rows before
1045
+ or after a sibling; Space/Enter grabs/releases the same handle and ArrowUp/ArrowDown moves it for
1046
+ keyboard users. `moveRow(id, targetId, position)` exposes the same guarded operation. Hierarchical
1047
+ rows only move among siblings, and an active local sort disables manual order. A cancelable
1048
+ `rowmove {row,id,target,targetId,position}` fires before `datachange`.
1049
+ - **Column visibility** — `columnVisibility: true` renders a chooser; `hiddenColumns` seeds it.
1050
+ `getVisibleColumns()`, `getHiddenColumns()`, `setColumnVisible()`, and `toggleColumn()` preserve
1051
+ caller column order and never allow the final visible column to disappear.
1009
1052
  <!-- /doc -->
1010
1053
 
1011
1054
  <!-- doc:grid -->
@@ -1015,9 +1058,11 @@ range select.
1015
1058
  without forking its implementation, so sorting, selection, hierarchy, responsive stacking,
1016
1059
  editable cells/rows, loading, events, and teardown keep the Table contract.
1017
1060
 
1018
- - **Constructor** — `new Grid(target, tableOptions)` accepts the complete `Table` API.
1061
+ - **Constructor** — `new Grid(target, tableOptions)` accepts the complete `Table` API and defaults
1062
+ editable cells to a single-click trigger.
1019
1063
  - **Preset** — `Grid.BillingItems(target, options)` supplies an editable hierarchical billing
1020
- schema for item, quantity, unit, unit price, currency, and line total. It returns a `Grid`.
1064
+ schema for item, quantity, unit, unit price, currency, and line total. It enables single-click
1065
+ editing, hierarchy, row reorder handles, and the column visibility chooser. It returns a `Grid`.
1021
1066
  - **Billing options** — `data`, `fields` key overrides, `units`, `currencies`, fallback `currency`,
1022
1067
  `locale`, `decimals`, `columnOverrides`, complete `columns` replacement, `lineTotal(row, changes)`,
1023
1068
  and `oneditcommit` in addition to ordinary Table options.
@@ -1068,6 +1113,35 @@ Declarative client-side filter bar producing a filtered array, commonly wired to
1068
1113
  - **Events** — `filter {rows, state}`.
1069
1114
  <!-- /doc -->
1070
1115
 
1116
+ <!-- doc:filter -->
1117
+ ### Filter — dynamic backend-neutral expressions
1118
+
1119
+ An authoring component for typed, nested filter expressions. It never evaluates application data
1120
+ or interpolates query text: it emits a versioned, JSON-safe AST for an allowlisted application
1121
+ adapter to compile into its own backend query language.
1122
+
1123
+ - **AST** — `{version: 1, root: {kind:'group', id, logic:'and'|'or', children}}`; children are
1124
+ stable-ID groups or `{kind:'condition', id, field, operator, value}` records. Unknown field or
1125
+ operator IDs survive parsing and render as unavailable/invalid instead of being silently lost.
1126
+ - **Fields** — `{id,label,type,operators?,defaultOperator?,choices?,loadChoices?,editor?,minQuery?,
1127
+ debounce?}`. Built-in types cover text, number/money, date/datetime, boolean, enum/status/
1128
+ priority/country/currency/unit/entity, and tags. Compatible operators and their arity come from
1129
+ the exported `filterOperators` matrix; applications may explicitly override/add operator IDs.
1130
+ - **Options** — `fields`, `value`, `operators`, `allowGroups: true`, `maxDepth: 3`,
1131
+ `maxConditions: 50`, `autoApply: false`, `readonly`, `disabled`, `applyLabel`, `clearLabel`.
1132
+ - **Methods** — `getValue()`, atomic `setValue()`, `addCondition()`, `addGroup()`, `update()`,
1133
+ `remove()`, `move()`, `validate()`, `apply()`, `clear()`, `focus()`, `setReadonly()`,
1134
+ `enable()`/`disable()`.
1135
+ - **Events** — `change {value,valid,errors,reason,nodeId}`, `apply {value}`, `invalid {errors}`,
1136
+ and abortable async-choice lifecycle `query`, `loaded`, `error`. `apply()` returns null and
1137
+ focuses the first invalid condition when validation fails.
1138
+ - **Pure exports** — `emptyFilterAst`, `filterCondition`, `filterGroup`, `parseFilterAst`,
1139
+ `cloneFilterAst`, `cloneFilterValue`, `stringifyFilterAst`, and `validateFilterAst`.
1140
+ - **Boundary** — keep the AST in saved views/audit state and compile it in a backend-specific
1141
+ adapter with an allowlist and bound parameters. Do not put SQL, URL query fragments, functions,
1142
+ DOM nodes, Dates, non-finite numbers, or other executable/non-JSON values into conditions.
1143
+ <!-- /doc -->
1144
+
1071
1145
  <!-- doc:tree -->
1072
1146
  ### TreeView
1073
1147
 
@@ -1278,10 +1352,13 @@ shows a fixed set, and `ValueList` takes free text with no catalogue. Unknown ID
1278
1352
  `setValues` still render as tags, so a stored selection survives a catalogue that has not loaded
1279
1353
  yet. Field type: `tagpicker`.
1280
1354
 
1281
- - **Options** — `items`, `values: []`, `valueKey: 'ID'`, `labelKey: 'name'`, `searchKeys`,
1355
+ - **Options** — `items`, `values: []`, `valueKey: 'ID'`, `labelKey: 'name'`, `iconKey: 'icon'`,
1356
+ `colorKey: 'color'`, `searchKeys`,
1282
1357
  `filter: 'local'|(query)=>Promise<items>`, `minQuery: 0`, `debounce: 200`, `allowCreate: false`,
1283
1358
  `max` (maximum tags; further options become `aria-disabled`), `closeOnSelect: false`,
1284
1359
  `placeholder`, `listHeight: 260`, `disabled`, `readonly`, `renderItem`, `renderTag`.
1360
+ - **Tag identity** — icon values accept a bundled icon name or Node; colors are scoped to the tag
1361
+ and its option through `--zx-tag-color`. Custom `renderItem`/`renderTag` remain authoritative.
1285
1362
  - **Methods** — `getValues()`, `getItems()`, `setValues(values, {silent})`, `addValue()`,
1286
1363
  `removeValue()`, `clear()`, `reset()`, `setItems()`, `isFull()`, `open()`/`close()`, `focus()`,
1287
1364
  `setReadonly()`, `enable()`/`disable()`.
@@ -1584,16 +1661,16 @@ inline; every rail state opens descendants toward the workspace in anchored flyo
1584
1661
 
1585
1662
  - **Component** — the base class: `on`/`off`/`once`/`emit`, `listen`, `toElement`, `msg`,
1586
1663
  `destroy`, and the static `Component.from(el)`.
1587
- - **DOM** — `h(tag, props, ...children)`, `h.raw(html)`, `htmlEscape`, `resolveElement`. The current
1588
- ZeyOS builder is also first-class: `__(tag#id.class, properties, content)` is the migration alias
1589
- for `compactElement()`. It preserves `D*` data, `S*` style, boolean/built-in property, attribute,
1664
+ - **DOM** — `h(tag, props, ...children)`, `h.raw(html)`, `htmlEscape`, `resolveElement`.
1665
+ `__(tag#id.class, properties, content)` is Zx's compact DOM builder and public alias for
1666
+ `compactElement()`. It preserves `D*` data, `S*` style, boolean/built-in property, attribute,
1590
1667
  and legacy `on*` callback rules while keeping content text-safe. `applyCompactProperties`,
1591
1668
  `appendContent`, `appendCompact` (parent return), `appendCompactChild` (child return), and
1592
1669
  `fragment(...children)` are the pure forms.
1593
1670
  - **Compact events** — `onCompactEvent`, `offCompactEvent`, and `fireCompactEvent` share one
1594
1671
  newest-first handler registry. Existing `DOM.on/remEvt/fireEvt` methods can delegate to this
1595
- triad. Zx never installs globals or alters DOM prototypes; the current application owns those
1596
- temporary seams while `UI.new*` implementations are replaced without rewriting `PG` routing.
1672
+ triad. Zx never installs globals or alters DOM prototypes; applications decide whether to expose
1673
+ these functions globally or compose them into their own UI and routing layer.
1597
1674
  - **Icons** — `icon(name, {size, label})` and `icons`; see the Icons section — bundled inline SVG
1598
1675
  by default, Font Awesome after an opt-in.
1599
1676
  - **Positioning** — `position(anchor, floating, {placement, offset, flip, matchWidth})` →