@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/CHANGELOG.md CHANGED
@@ -8,6 +8,50 @@ and friends remain the complete record.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 4.1.0 — 2026-08-25
12
+
13
+ ### Added
14
+
15
+ - **`AppIcon`.** A reusable application-identity surface with safe semantic colours, badges,
16
+ accessible labelled and decorative modes, and an optional CSS-only translucent glass material.
17
+ The ZeyOS adapter supplies presets for all 29 current applications while keeping glyph loading
18
+ and routing application-owned.
19
+ - **`Filter`.** A backend-neutral visual expression builder with a versioned JSON-safe AST,
20
+ nested AND/OR groups, typed operators and value editors, abortable asynchronous choices,
21
+ structural and semantic validation, defensive limits, deterministic keyboard focus, and
22
+ valid-only Apply events. Query compilation, saved views, and URL state remain adapter concerns.
23
+ - **Richer entity and tag selection.** `Select.entity()` supports optional recent items and an
24
+ application-owned create command; `TagPicker` now renders native item icons and semantic colours
25
+ without overriding a custom renderer.
26
+
27
+ ### Changed
28
+
29
+ - **Transaction grids are faster to operate.** `Grid.BillingItems()` and `Table` now support
30
+ single-click typed editing, opt-in double-click compatibility, same-parent row reordering by
31
+ drag or keyboard, hierarchy-safe branch moves, and accessible column visibility controls.
32
+ - **ZeyOS identity is consistent across workflows.** Launcher application tiles use the new
33
+ AppIcons, while Launcher recents and entity-select results use module/entity identities and
34
+ colours rather than generic placeholders.
35
+ - **The documentation shell is easier to navigate.** Global search is centred in every header and
36
+ reports full breadcrumb paths; component groups are alphabetized, application-shell primitives
37
+ are collected under Layout, narrow API tables remain locally scrollable, and public navigation
38
+ and footer links are reduced to their canonical destinations.
39
+ - **Theme Studio now applies complete recipes.** Presets change typography, tint, radius, density,
40
+ control sizing, and glass strength in addition to colour; the house preset is named `ZeyOS`.
41
+
42
+ ### Fixed
43
+
44
+ - Select preset chevrons retain their alignment across theme recipes and enhanced
45
+ `Select.entity()` targets restore their exact original DOM when destroyed.
46
+ - Floating messages default to a content-sized upper-right glass surface instead of stretching
47
+ with the document, with opaque and reduced-transparency fallbacks.
48
+ - Filter mutations preserve keyboard focus and accessible announcements; asynchronous editors
49
+ expose busy state and abort stale work.
50
+ - Row editing no longer targets hidden columns or hijacks buttons and links rendered inside cells;
51
+ column toggles retain focus and row drags do not export record identifiers as plain text.
52
+ - Application and tag colour overrides reject CSS image and URL values before reaching public
53
+ custom properties.
54
+
11
55
  ## 4.0.0 — 2026-08-24
12
56
 
13
57
  ### Added
package/DESIGN-SYSTEM.md CHANGED
@@ -33,16 +33,72 @@ and navigation remain application-owned.
33
33
  | Forms and fieldsets | Covered, including component-backed field adapters | Use for record editors; keep validation/business rules application-owned. |
34
34
  | Dialogs, sheets, dropdowns, context menus, tooltips, messages | Covered | Consolidate duplicate menu behavior before building account/launcher surfaces. |
35
35
  | Tabs, panels, toolbar, breadcrumbs, stepper, split view, dock | Covered | Compose layouts; do not let navigation components own routes. |
36
- | Sorting, selection, filtering, pagination, trees/finder | Covered | Suitable for generic data screens. |
37
- | Editable billing/transaction rows | Covered at generic-grid level | `Grid.BillingItems()` composes typed editable currency/unit columns and flat hierarchy over `Table`. Tax, subtotal, posting, rounding, and persistence stay domain code. |
36
+ | Sorting, selection, dynamic/local filtering, pagination, trees/finder | Covered | `Filter` authors typed nested expressions; `DataFilter` executes simple local predicates. Backend query compilation stays in adapters. |
37
+ | Editable billing/transaction rows | Covered at generic-grid level | `Grid.BillingItems()` composes single-click typed currency/unit editors, flat hierarchy, same-parent row movement, and column visibility over `Table`. Tax, subtotal, posting, rounding, and persistence stay domain code. |
38
38
  | Global launcher/search | Covered | `Launcher` combines an application grid, current/pinned state, recent records, and abortable grouped sources. The optional ZeyOS adapter maps the complete 29-app shell catalogue while the host retains permissions, icons, and routing. |
39
39
  | Account/profile/preferences menu | Covered at presentation level | `Avatar` and `AccountMenu` provide identity and action UI. The host supplies permitted actions and owns preferences, authentication, and logout. |
40
40
  | Persistent app rail / side navigation | Covered | One route-agnostic `AppSidebar` owns expanded, minimized, and horizontal presentations. Only expanded vertical branches render inline; every rail presentation uses flyouts. |
41
41
  | Charts and analytical summaries | Covered through an adapter | `Chart` is engine-neutral and accessible; `ChartJsAdapter` accepts an injected Chart.js 4 engine. Chart.js is documentation/test tooling only. |
42
- | Local product/module icons | Partial | Use bundled icons or registered local assets; do not require the optional remote Font Awesome kit. |
42
+ | Local product/module icons | Covered | `AppIcon` is generic; `zeyosAppIcon()` maps every current ZeyOS module/entity identity and accepts an offline glyph override. The optional remote Font Awesome kit is never loaded automatically. |
43
43
  | Domain entity selectors/cards/links | Covered at generic presentation level | `Card` provides media, native primary links, secondary actions, and metadata; applications own entity routing and policy. |
44
44
  | Persistent notification/activity center | Missing, lower priority | `Message` covers ephemeral feedback, not read/unread activity history. |
45
45
 
46
+ ## Current ZeyOS delta audit (25 August 2026)
47
+
48
+ This is a source audit, not a visual inventory. The comparison uses the current ZeyOS DOM/UI
49
+ implementation as evidence: its generic control catalogue is concentrated in
50
+ [`4-ui.orig.js`](../zeyos/ext/global.orig/4-ui.orig.js), configurable index-column management in
51
+ [`4-pg.orig.js`](../zeyos/ext/global.orig/4-pg.orig.js#L3464), and the multi-view scheduling surface
52
+ in [`calendar.orig.js`](../zeyos/ext/mod/calendar.orig.js#L61). Capabilities already represented by
53
+ a Zx component or a documented composition are not counted as gaps.
54
+
55
+ ### Already closed by Zx core
56
+
57
+ | ZeyOS behavior | Source evidence | Xenon/Zx coverage |
58
+ | --- | --- | --- |
59
+ | Dynamic search/filter authoring and saved filter controls | [`UI.newSearch`](../zeyos/ext/global.orig/4-ui.orig.js#L3378) and [`UI.newContainerFilters`](../zeyos/ext/global.orig/4-ui.orig.js#L7723) | `Filter` supplies a typed/versioned AST editor; `DataFilter` remains the lightweight local-row helper. A compiler and persistence service are intentionally not core. |
60
+ | Editable transaction rows and row movement | [`_newTblAddColEdit`](../zeyos/ext/global.orig/4-ui.orig.js#L5613) and [`newTblItems`](../zeyos/ext/global.orig/4-ui.orig.js#L6070) | `Table`/`Grid.BillingItems()` cover typed editing, currencies/units, hierarchy, single-click entry, same-parent drag/keyboard reorder, and show/hide columns. |
61
+ | Entity selection, priority, status, and permission choices | [`newSelEntity`](../zeyos/ext/global.orig/4-ui.orig.js#L3296), [`newSelPriority`](../zeyos/ext/global.orig/4-ui.orig.js#L3101), and the surrounding Select family | `Select.entity()`, `.priority()`, `.status()`, and `.permission()` cover the interaction contract; the ZeyOS adapter owns schemas and localized catalogues. |
62
+ | Application launcher and module/entity identity | [`newExpLst`](../zeyos/ext/global.orig/4-ui.orig.js#L4355) and entity/module item builders through line 4457 | `Launcher`, `AppIcon`, and the optional ZeyOS adapter cover the full app catalogue, current/pinned state, recent records, grouped abortable search, forks/weblets, and injected routing. |
63
+ | Expanded/minimized/horizontal app navigation | Current shell navigation plus the same module identity catalogue | One public `AppSidebar` owns all presentations. The rail renderer is an internal presenter, not a second component; only expanded vertical branches render inline and rail branches use anchored flyouts. |
64
+ | Toasts, loading, cards, dialogs, forms, grids, charts, and layout primitives | Multiple `newContainer*`, input, table, and progress families in `4-ui.orig.js` | Covered by existing Zx components and documented composition; these are not delta items merely because the legacy factory names differ. |
65
+
66
+ ### Recommended additions to Zx core
67
+
68
+ | Priority | Missing reusable contract | Evidence and proposed boundary |
69
+ | --- | --- | --- |
70
+ | P0 | **EntityRef** | ZeyOS has a large family of linked/static entity presentations with subtitle, owner/group/assignee context, fork, and record actions: [`newLinkEntity*`](../zeyos/ext/global.orig/4-ui.orig.js#L225) and [`newEntityStatic`/`newEntity*`](../zeyos/ext/global.orig/4-ui.orig.js#L8262). A generic `EntityRef` should own icon/title/subtitle/metadata/link/action semantics; ZeyOS routing, permissions, and context menus remain injected. `Card` is too large for dense table/feed rows. |
71
+ | P0 | **FileItem / FileList** | Existing-file, temporary-file, waiting/progress, MIME, size, and download rows recur in [`newFile`, `newFileTemp`, and `newFileWait`](../zeyos/ext/global.orig/4-ui.orig.js#L8158). `FieldUpload` owns selection/upload but not durable file presentation. Core should render file identity/state/actions; transport, authorization, preview, and download URLs stay with the host. |
72
+ | P0 | **ActivityList / ActivityItem** | [`newFeed`](../zeyos/ext/global.orig/4-ui.orig.js#L8704) combines chronological records, comments, attachments, likes, and incremental updates. Core should own an accessible chronological list, grouping, loading/empty state, and action slots. Posting, reactions, channels, permissions, and persistence remain application code. |
73
+ | P1 | **Scheduler** | The current calendar implements agenda/day/week/month/year views, timed and spanning items, keyboard/routing affordances, and dense collision layout in [`calendar.orig.js`](../zeyos/ext/mod/calendar.orig.js#L61). A generic scheduler is a real core gap, but should be a separate workstream with interval-layout and accessibility invariants; recurrence, DAV, permissions, and routes are adapters/application logic. |
74
+ | P1 | **FacetList** | Counted activity/status/tag/entity side filters appear in [`newNavActivityStatus`](../zeyos/ext/global.orig/4-ui.orig.js#L3967), `newNavTag`, and `newNavEntity`. `Filter` is an expression author, not a compact persistent facet navigator. Core should own selection/count/collapse/loading behavior; query mapping stays injected. |
75
+ | P1 | **EditorHost** | ZeyOS uses plain/Markdown/contenteditable/iframe editors through [`newEditor`](../zeyos/ext/global.orig/4-ui.orig.js#L1607) and `newFrameEditor`. Like `Chart`, core should provide labels, toolbar/action slots, value/lifecycle, dirty/read-only/error states, and an injected engine adapter. Sanitization and document/media policy must not be implicit core behavior. |
76
+ | P1 | **ColorPicker** | The reusable swatch picker is [`newCl`](../zeyos/ext/global.orig/4-ui.orig.js#L3753). A dependency-free accessible swatch/custom-color picker belongs in core and would also serve the Theme Studio. |
77
+ | P2 | **Metric / Summary** | [`newSummary`](../zeyos/ext/global.orig/4-ui.orig.js#L7312) repeatedly formats counts, money, timing, size, stock, and relative values. These can currently be composed from Card and format helpers, but a small semantic Metric/DescriptionList pair would remove repeated markup without importing business formulas. |
78
+ | P2 | **RecordPreview** | Typed preview builders for appointments, contacts, links, messages, tasks, and tickets start at [`newPreviewForm`](../zeyos/ext/global.orig/4-ui.orig.js#L9589). Core should offer a generic preview anatomy or engine-neutral preview host, not one component per ZeyOS entity. |
79
+ | P2 | **Shortcut / Kbd** | Launcher and menus expose shortcuts but Zx has no reusable semantic `<kbd>` treatment. A tiny factory is justified once shortcuts appear outside those components. |
80
+
81
+ ### Adapter work, not new core components
82
+
83
+ - A **ZeyOS Filter compiler** mapping allowlisted AST field/operator IDs to the server's plural
84
+ `filters` query shape, plus a parser for stored legacy filter state. Compilation must use bound/
85
+ structured values and never accept raw SQL or URL fragments.
86
+ - A shared **ZeyOS field/status/priority catalogue** built from runtime schemas and localization,
87
+ feeding `Field`, `Filter`, `Select.status()`, and `Select.priority()` without hard-coding
88
+ entity-specific policy in core.
89
+ - **EntityRef/FileItem/ActivityList adapters** that resolve routes, icons, permissions, server
90
+ payloads, and action menus while leaving their visual/interaction contracts reusable.
91
+ - Optional persistence connectors for column visibility/order, sidebar state, saved filters, recent
92
+ items, and theme preferences. Core should expose serializable state and events only.
93
+
94
+ ### Deliberately application-owned
95
+
96
+ Routing/history, authentication/session/logout, authorization, server transport/caching, saved-view
97
+ storage, audit policy, transaction tax/discount/subtotal/posting formulas, optimistic writes/undo,
98
+ calendar recurrence/DAV synchronization, rich-text sanitization, and activity actions are not
99
+ design-system responsibilities. Moving them into Zx would reproduce ZeyOS application logic inside
100
+ the library and make Xenon less reusable.
101
+
46
102
  ### Launcher contract
47
103
 
48
104
  The launcher searches applications, recent records, and grouped remote record types. The generic
@@ -0,0 +1,88 @@
1
+ import { Component } from '../../core/component.js';
2
+ export type AppIconOptions = {
3
+ /**
4
+ * Icon name, node, or lazy renderer.
5
+ */
6
+ icon?: string | Node | (() => string | Node) | null;
7
+ /**
8
+ * Identity colour; falls back to the accent colour.
9
+ */
10
+ color?: string | null;
11
+ /**
12
+ * Edge length in pixels or any CSS length.
13
+ */
14
+ size?: number | string;
15
+ /**
16
+ * Glyph size.
17
+ */
18
+ iconSize?: number | string;
19
+ /**
20
+ * Accessible image label; null makes the icon decorative.
21
+ */
22
+ label?: string | null;
23
+ /**
24
+ * Optional corner badge.
25
+ */
26
+ badge?: string | number | null;
27
+ /**
28
+ * Whether the icon represents the active application.
29
+ */
30
+ selected?: boolean;
31
+ /**
32
+ * CSS-first glass treatment.
33
+ */
34
+ glass?: false | 'subtle' | 'strong';
35
+ /**
36
+ * Additional class names.
37
+ */
38
+ class?: string | string[];
39
+ };
40
+ /**
41
+ * @typedef {Object} AppIconOptions
42
+ * @property {string|Node|(()=>string|Node)|null} [icon=null] Icon name, node, or lazy renderer.
43
+ * @property {string|null} [color=null] Identity colour; falls back to the accent colour.
44
+ * @property {number|string} [size=36] Edge length in pixels or any CSS length.
45
+ * @property {number|string} [iconSize='52%'] Glyph size.
46
+ * @property {string|null} [label=null] Accessible image label; null makes the icon decorative.
47
+ * @property {string|number|null} [badge=null] Optional corner badge.
48
+ * @property {boolean} [selected=false] Whether the icon represents the active application.
49
+ * @property {false|'subtle'|'strong'} [glass='subtle'] CSS-first glass treatment.
50
+ * @property {string|string[]} [class] Additional class names.
51
+ */
52
+ /**
53
+ * Stable application-identity tile with progressive CSS glass treatment.
54
+ * @extends {Component<AppIconOptions>}
55
+ */
56
+ export declare class AppIcon extends Component<AppIconOptions> {
57
+ _createdRoot: boolean;
58
+ _snapshot: import("../../core/dom.js").TargetSnapshot;
59
+ _state: {
60
+ icon?: string | Node | (() => string | Node) | null;
61
+ color?: string | null;
62
+ size?: number | string;
63
+ iconSize?: number | string;
64
+ label?: string | null;
65
+ badge?: string | number | null;
66
+ selected?: boolean;
67
+ glass?: false | 'subtle' | 'strong';
68
+ class?: string | string[];
69
+ };
70
+ _appliedClasses: any[] | string[];
71
+ static cssName: string;
72
+ /** @type {Readonly<AppIconOptions>} */
73
+ static defaults: Readonly<AppIconOptions>;
74
+ /** @returns {HTMLElement} */
75
+ render(): HTMLElement;
76
+ /** Updates identity or presentation without replacing the root. @param {Partial<AppIconOptions>} values @returns {this} */
77
+ set(values?: Partial<AppIconOptions>): this;
78
+ /** Restores an enhanced target. @returns {void} */
79
+ destroy(): void;
80
+ /** @returns {void} */
81
+ _sync(): void;
82
+ }
83
+ /**
84
+ * Creates an AppIcon element without retaining a component lifecycle handle.
85
+ * @param {AppIconOptions} [options={}] Icon options.
86
+ * @returns {HTMLElement}
87
+ */
88
+ export declare function appIcon(options?: AppIconOptions): HTMLElement;
@@ -0,0 +1,87 @@
1
+ export type FilterJson = null | boolean | number | string | FilterJson[] | {
2
+ [key: string]: FilterJson;
3
+ };
4
+ export type FilterCondition = {
5
+ kind: 'condition';
6
+ id: string;
7
+ field: string;
8
+ operator: string;
9
+ value: FilterJson;
10
+ };
11
+ export type FilterGroup = {
12
+ kind: 'group';
13
+ id: string;
14
+ logic: 'and' | 'or';
15
+ children: FilterNode[];
16
+ };
17
+ export type FilterNode = FilterCondition | FilterGroup;
18
+ export type FilterAst = {
19
+ version: 1;
20
+ root: FilterGroup;
21
+ };
22
+ export type FilterOperator = {
23
+ id: string;
24
+ label: string;
25
+ arity: 'none' | 'single' | 'pair' | 'many';
26
+ types: string[];
27
+ };
28
+ export type FilterField = {
29
+ id: string;
30
+ label: string;
31
+ type: string;
32
+ operators?: string[];
33
+ defaultOperator?: string;
34
+ };
35
+ export type FilterValidationError = {
36
+ nodeId: string;
37
+ code: string;
38
+ message: string;
39
+ };
40
+ /** @typedef {null|boolean|number|string|FilterJson[]|{[key:string]: FilterJson}} FilterJson */
41
+ /** @typedef {{kind:'condition', id:string, field:string, operator:string, value:FilterJson}} FilterCondition */
42
+ /** @typedef {{kind:'group', id:string, logic:'and'|'or', children:FilterNode[]}} FilterGroup */
43
+ /** @typedef {FilterCondition|FilterGroup} FilterNode */
44
+ /** @typedef {{version:1, root:FilterGroup}} FilterAst */
45
+ /** @typedef {{id:string, label:string, arity:'none'|'single'|'pair'|'many', types:string[]}} FilterOperator */
46
+ /** @typedef {{id:string, label:string, type:string, operators?:string[], defaultOperator?:string}} FilterField */
47
+ /** @typedef {{nodeId:string, code:string, message:string}} FilterValidationError */
48
+ /** Core operators are application-neutral IDs; an adapter maps them to executable query syntax. */
49
+ export declare const filterOperators: readonly FilterOperator[];
50
+ /** @returns {FilterAst} */
51
+ export declare function emptyFilterAst(): FilterAst;
52
+ /** @param {Partial<FilterCondition>} [initial={}] @returns {FilterCondition} */
53
+ export declare function filterCondition(initial?: Partial<FilterCondition>): FilterCondition;
54
+ /**
55
+ * Returns a defensive JSON-safe copy suitable for a condition value.
56
+ * @param {unknown} value
57
+ * @returns {FilterJson}
58
+ */
59
+ export declare function cloneFilterValue(value: unknown): FilterJson;
60
+ /** @param {'and'|'or'} [logic='and'] @param {FilterNode[]} [children=[]] @returns {FilterGroup} */
61
+ export declare function filterGroup(logic?: 'and' | 'or', children?: FilterNode[]): FilterGroup;
62
+ /**
63
+ * Parses untrusted state into a defensive JSON-safe AST. It rejects structural corruption but
64
+ * deliberately keeps unknown field/operator IDs for semantic validation and migration UI.
65
+ * @param {unknown} value
66
+ * @param {{maxDepth?:number, maxConditions?:number}} [limits={}]
67
+ * @returns {FilterAst}
68
+ */
69
+ export declare function parseFilterAst(value: unknown, limits?: {
70
+ maxDepth?: number;
71
+ maxConditions?: number;
72
+ }): FilterAst;
73
+ /** @param {FilterAst} value @returns {FilterAst} */
74
+ export declare function cloneFilterAst(value: FilterAst): FilterAst;
75
+ /** @param {FilterAst} value @returns {string} */
76
+ export declare function stringifyFilterAst(value: FilterAst): string;
77
+ /**
78
+ * Performs field/operator/arity validation without compiling or executing the expression.
79
+ * @param {FilterAst} ast
80
+ * @param {FilterField[]} fields
81
+ * @param {FilterOperator[]} [operators=filterOperators]
82
+ * @returns {{valid:boolean, errors:FilterValidationError[]}}
83
+ */
84
+ export declare function validateFilterAst(ast: FilterAst, fields: FilterField[], operators?: FilterOperator[]): {
85
+ valid: boolean;
86
+ errors: FilterValidationError[];
87
+ };
@@ -0,0 +1,331 @@
1
+ import { Component } from '../../core/component.js';
2
+ export type FilterAst = import('./filter-model.js').FilterAst;
3
+ export type FilterField = {
4
+ /**
5
+ * Stable allowlisted field ID.
6
+ */
7
+ id: string;
8
+ /**
9
+ * Display label.
10
+ */
11
+ label: string;
12
+ /**
13
+ * Editor/operator type.
14
+ */
15
+ type: 'text' | 'number' | 'money' | 'date' | 'datetime' | 'boolean' | 'enum' | 'status' | 'priority' | 'country' | 'currency' | 'unit' | 'entity' | 'tags' | 'custom';
16
+ /**
17
+ * Explicit allowlisted operator IDs.
18
+ */
19
+ operators?: string[];
20
+ /**
21
+ * Initial operator ID.
22
+ */
23
+ defaultOperator?: string;
24
+ /**
25
+ * Static choices.
26
+ */
27
+ choices?: Array<{
28
+ value: string | number | boolean;
29
+ label: string;
30
+ icon?: Node | string;
31
+ color?: string;
32
+ }>;
33
+ /**
34
+ * Async choices.
35
+ */
36
+ loadChoices?: (context: {
37
+ query: string;
38
+ signal: AbortSignal;
39
+ conditionId: string;
40
+ field: FilterField;
41
+ }) => Promise<Array<{
42
+ value: string | number | boolean;
43
+ label: string;
44
+ }>> | Array<{
45
+ value: string | number | boolean;
46
+ label: string;
47
+ }>;
48
+ /**
49
+ * Custom editor.
50
+ */
51
+ editor?: (target: Element, context: {
52
+ value: unknown;
53
+ setValue: (value: unknown) => void;
54
+ field: FilterField;
55
+ conditionId: string;
56
+ }) => ({
57
+ destroy?: () => void;
58
+ } | void);
59
+ /**
60
+ * Async minimum query length.
61
+ */
62
+ minQuery?: number;
63
+ /**
64
+ * Async debounce in milliseconds.
65
+ */
66
+ debounce?: number;
67
+ };
68
+ export type FilterOptions = {
69
+ /**
70
+ * Field definitions.
71
+ */
72
+ fields: FilterField[];
73
+ /**
74
+ * Initial expression.
75
+ */
76
+ value?: FilterAst;
77
+ /**
78
+ * Additional/overridden operators.
79
+ */
80
+ operators?: Array<{
81
+ id: string;
82
+ label: string;
83
+ arity: 'none' | 'single' | 'pair' | 'many';
84
+ types: string[];
85
+ }>;
86
+ /**
87
+ * Whether nested groups may be authored.
88
+ */
89
+ allowGroups?: boolean;
90
+ /**
91
+ * Maximum nested group depth.
92
+ */
93
+ maxDepth?: number;
94
+ /**
95
+ * Maximum condition count.
96
+ */
97
+ maxConditions?: number;
98
+ /**
99
+ * Emit apply after every valid mutation.
100
+ */
101
+ autoApply?: boolean;
102
+ /**
103
+ * Prevent mutations while keeping values readable.
104
+ */
105
+ readonly?: boolean;
106
+ /**
107
+ * Disable all controls.
108
+ */
109
+ disabled?: boolean;
110
+ /**
111
+ * Apply button label.
112
+ */
113
+ applyLabel?: string;
114
+ /**
115
+ * Clear button label.
116
+ */
117
+ clearLabel?: string;
118
+ /**
119
+ * Draft-change listener.
120
+ */
121
+ onchange?: (event: CustomEvent<Record<string, unknown>>) => void;
122
+ /**
123
+ * Valid-apply listener.
124
+ */
125
+ onapply?: (event: CustomEvent<Record<string, unknown>>) => void;
126
+ /**
127
+ * Invalid-apply listener.
128
+ */
129
+ oninvalid?: (event: CustomEvent<Record<string, unknown>>) => void;
130
+ /**
131
+ * Async query listener.
132
+ */
133
+ onquery?: (event: CustomEvent<Record<string, unknown>>) => void;
134
+ /**
135
+ * Async loaded listener.
136
+ */
137
+ onloaded?: (event: CustomEvent<Record<string, unknown>>) => void;
138
+ /**
139
+ * Async failure listener.
140
+ */
141
+ onerror?: (event: CustomEvent<Record<string, unknown>>) => void;
142
+ };
143
+ /** @typedef {import('./filter-model.js').FilterAst} FilterAst */
144
+ /**
145
+ * @typedef {Object} FilterField
146
+ * @property {string} id Stable allowlisted field ID.
147
+ * @property {string} label Display label.
148
+ * @property {'text'|'number'|'money'|'date'|'datetime'|'boolean'|'enum'|'status'|'priority'|'country'|'currency'|'unit'|'entity'|'tags'|'custom'} type Editor/operator type.
149
+ * @property {string[]} [operators] Explicit allowlisted operator IDs.
150
+ * @property {string} [defaultOperator] Initial operator ID.
151
+ * @property {Array<{value:string|number|boolean,label:string,icon?:Node|string,color?:string}>} [choices] Static choices.
152
+ * @property {(context:{query:string,signal:AbortSignal,conditionId:string,field:FilterField})=>Promise<Array<{value:string|number|boolean,label:string}>>|Array<{value:string|number|boolean,label:string}>} [loadChoices] Async choices.
153
+ * @property {(target:Element, context:{value:unknown,setValue:(value:unknown)=>void,field:FilterField,conditionId:string})=>({destroy?:()=>void}|void)} [editor] Custom editor.
154
+ * @property {number} [minQuery=0] Async minimum query length.
155
+ * @property {number} [debounce=200] Async debounce in milliseconds.
156
+ */
157
+ /**
158
+ * @typedef {Object} FilterOptions
159
+ * @property {FilterField[]} fields Field definitions.
160
+ * @property {FilterAst} [value] Initial expression.
161
+ * @property {Array<{id:string,label:string,arity:'none'|'single'|'pair'|'many',types:string[]}>} [operators] Additional/overridden operators.
162
+ * @property {boolean} [allowGroups=true] Whether nested groups may be authored.
163
+ * @property {number} [maxDepth=3] Maximum nested group depth.
164
+ * @property {number} [maxConditions=50] Maximum condition count.
165
+ * @property {boolean} [autoApply=false] Emit apply after every valid mutation.
166
+ * @property {boolean} [readonly=false] Prevent mutations while keeping values readable.
167
+ * @property {boolean} [disabled=false] Disable all controls.
168
+ * @property {string} [applyLabel='Apply filters'] Apply button label.
169
+ * @property {string} [clearLabel='Clear'] Clear button label.
170
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onchange] Draft-change listener.
171
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onapply] Valid-apply listener.
172
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [oninvalid] Invalid-apply listener.
173
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onquery] Async query listener.
174
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onloaded] Async loaded listener.
175
+ * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onerror] Async failure listener.
176
+ */
177
+ /**
178
+ * Backend-neutral dynamic filter expression editor.
179
+ * @fires Filter#change
180
+ * @fires Filter#apply
181
+ * @fires Filter#invalid
182
+ * @fires Filter#query
183
+ * @fires Filter#loaded
184
+ * @fires Filter#error
185
+ * @extends {Component<FilterOptions>}
186
+ */
187
+ export declare class Filter extends Component<FilterOptions> {
188
+ _createdRoot: boolean;
189
+ _snapshot: import("../../core/dom.js").TargetSnapshot;
190
+ _fields: {
191
+ id: string;
192
+ label: string;
193
+ type: 'text' | 'number' | 'money' | 'date' | 'datetime' | 'boolean' | 'enum' | 'status' | 'priority' | 'country' | 'currency' | 'unit' | 'entity' | 'tags' | 'custom';
194
+ operators?: string[];
195
+ defaultOperator?: string;
196
+ choices?: Array<{
197
+ value: string | number | boolean;
198
+ label: string;
199
+ icon?: Node | string;
200
+ color?: string;
201
+ }>;
202
+ loadChoices?: (context: {
203
+ query: string;
204
+ signal: AbortSignal;
205
+ conditionId: string;
206
+ field: FilterField;
207
+ }) => Promise<Array<{
208
+ value: string | number | boolean;
209
+ label: string;
210
+ }>> | Array<{
211
+ value: string | number | boolean;
212
+ label: string;
213
+ }>;
214
+ editor?: (target: Element, context: {
215
+ value: unknown;
216
+ setValue: (value: unknown) => void;
217
+ field: FilterField;
218
+ conditionId: string;
219
+ }) => ({
220
+ destroy?: () => void;
221
+ } | void);
222
+ minQuery?: number;
223
+ debounce?: number;
224
+ }[];
225
+ _operators: any[];
226
+ _readonly: boolean;
227
+ _disabled: boolean;
228
+ _async: Map<any, any>;
229
+ _customEditors: any[];
230
+ _value: import("./filter-model.js").FilterAst;
231
+ _renderVersion: any;
232
+ static cssName: string;
233
+ /** @type {Readonly<FilterOptions>} */
234
+ static defaults: Readonly<FilterOptions>;
235
+ /** @returns {HTMLElement} */
236
+ render(): HTMLElement;
237
+ /** Returns a defensive AST copy. @returns {FilterAst} */
238
+ getValue(): FilterAst;
239
+ /** Atomically replaces the AST. @param {unknown} value @param {{silent?:boolean}} [options={}] @returns {this} */
240
+ setValue(value: unknown, { silent }?: {
241
+ silent?: boolean;
242
+ }): this;
243
+ /** Adds a condition to a group. @param {string|null} [parentId=null] @param {Record<string,unknown>} [initial={}] @returns {string|null} */
244
+ addCondition(parentId?: string | null, initial?: Record<string, unknown>): string | null;
245
+ /** Adds a nested group. @param {string|null} [parentId=null] @param {'and'|'or'} [logic='and'] @returns {string|null} */
246
+ addGroup(parentId?: string | null, logic?: 'and' | 'or'): string | null;
247
+ /** Updates a node by stable ID. @param {string} id @param {Record<string,unknown>} patch @returns {this} */
248
+ update(id: string, patch?: Record<string, unknown>): this;
249
+ /** Removes a non-root node. @param {string} id @returns {this} */
250
+ remove(id: string): this;
251
+ /** Moves a node into a group while preserving its identity. @param {string} id @param {string} parentId @param {number} index @returns {this} */
252
+ move(id: string, parentId: string, index: number): this;
253
+ /** Returns semantic validity and errors. @returns {{valid:boolean,errors:Array<Record<string,string>>}} */
254
+ validate(): {
255
+ valid: boolean;
256
+ errors: Array<Record<string, string>>;
257
+ };
258
+ /** Applies only a valid AST. @returns {FilterAst|null} */
259
+ apply(): FilterAst | null;
260
+ /** Clears all conditions. @param {{silent?:boolean}} [options={}] @returns {this} */
261
+ clear({ silent }?: {
262
+ silent?: boolean;
263
+ }): this;
264
+ /** Focuses a condition's field or the Add button. @param {string|null} [id=null] @returns {this} */
265
+ focus(id?: string | null): this;
266
+ /** @param {boolean} readonly @returns {this} */
267
+ setReadonly(readonly: boolean): this;
268
+ /** @returns {this} */
269
+ enable(): this;
270
+ /** @returns {this} */
271
+ disable(): this;
272
+ /** @returns {void} */
273
+ destroy(): void;
274
+ /** @returns {void} */
275
+ _render(): void;
276
+ /** @param {any} group @param {number} depth @param {boolean} root @returns {HTMLElement} */
277
+ _renderGroup(group: any, depth: number, root: boolean): HTMLElement;
278
+ /** @param {any} node @returns {HTMLElement} */
279
+ _renderCondition(node: any): HTMLElement;
280
+ /** @param {any} node @param {FilterField|null} field @param {any} operator @returns {HTMLElement} */
281
+ _renderValue(node: any, field: FilterField | null, operator: any): HTMLElement;
282
+ /** @param {Event} event @returns {void} */
283
+ _handleChange(event: Event): void;
284
+ /** @param {Event} event @returns {void} */
285
+ _handleInput(event: Event): void;
286
+ /** @param {HTMLInputElement|HTMLSelectElement} target @returns {void} */
287
+ _valueInput(target: HTMLInputElement | HTMLSelectElement): void;
288
+ /** @param {MouseEvent} event @returns {void} */
289
+ _handleClick(event: MouseEvent): void;
290
+ /** @param {string} id @param {any} value @returns {void} */
291
+ _setNodeValue(id: string, value: any): void;
292
+ /** @param {string} reason @param {string} nodeId @returns {void} */
293
+ _changed(reason: string, nodeId: string): void;
294
+ /** @param {string} id @param {FilterField} field @param {string} query @returns {void} */
295
+ _scheduleChoices(id: string, field: FilterField, query: string): void;
296
+ /** @param {string} id @returns {void} */
297
+ _abort(id: string): void;
298
+ /** @param {string} id @param {boolean} busy @returns {void} */
299
+ _setAsyncBusy(id: string, busy: boolean): void;
300
+ /** @param {any} node @returns {void} */
301
+ _abortTree(node: any): void;
302
+ /** @returns {void} */
303
+ _abortAll(): void;
304
+ /** @returns {void} */
305
+ _destroyEditors(): void;
306
+ /** @returns {boolean} */
307
+ _blocked(): boolean;
308
+ /** @returns {boolean} */
309
+ _canMutate(): boolean;
310
+ /** @returns {{maxDepth:number,maxConditions:number}} */
311
+ _limits(): {
312
+ maxDepth: number;
313
+ maxConditions: number;
314
+ };
315
+ /** @param {string} id @returns {FilterField|null} */
316
+ _field(id: string): FilterField | null;
317
+ /** @param {string} id @returns {any} */
318
+ _operator(id: string): any;
319
+ /** @param {FilterField|null} field @returns {any[]} */
320
+ _operatorsFor(field: FilterField | null): any[];
321
+ /** @param {FilterField|null} field @returns {string|null} */
322
+ _defaultOperator(field: FilterField | null): string | null;
323
+ /** @param {string} text @returns {void} */
324
+ _announce(text: string): void;
325
+ }
326
+ /** @event Filter#change @type {CustomEvent<Record<string,unknown>>} */
327
+ /** @event Filter#apply @type {CustomEvent<{value:FilterAst}>} */
328
+ /** @event Filter#invalid @type {CustomEvent<Record<string,unknown>>} */
329
+ /** @event Filter#query @type {CustomEvent<Record<string,unknown>>} */
330
+ /** @event Filter#loaded @type {CustomEvent<Record<string,unknown>>} */
331
+ /** @event Filter#error @type {CustomEvent<Record<string,unknown>>} */