@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.
- package/CHANGELOG.md +44 -0
- package/DESIGN-SYSTEM.md +59 -3
- package/dist/types/components/app-icon/app-icon.d.ts +88 -0
- package/dist/types/components/filter/filter-model.d.ts +87 -0
- package/dist/types/components/filter/filter.d.ts +331 -0
- package/dist/types/components/select/select.d.ts +46 -4
- package/dist/types/components/table/table.d.ts +103 -0
- package/dist/types/components/tag-picker/tag-picker.d.ts +16 -0
- package/dist/types/core/dom.d.ts +1 -1
- package/dist/types/core/util.d.ts +9 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/zeyos/icons.d.ts +19 -2
- package/dist/types/zeyos/index.d.ts +1 -1
- package/dist/types/zeyos/select.d.ts +2 -0
- package/dist/zx-zeyos.esm.js +702 -75
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +415 -4
- package/dist/zx.esm.js +1568 -115
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +1568 -115
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +379 -0
- package/docs/llms.md +93 -16
- package/docs/llms.txt +11 -9
- package/package.json +1 -1
- package/src/components/app-icon/app-icon.css +105 -0
- package/src/components/app-icon/app-icon.js +134 -0
- package/src/components/filter/filter-model.js +226 -0
- package/src/components/filter/filter.css +147 -0
- package/src/components/filter/filter.js +554 -0
- package/src/components/grid/grid.css +1 -1
- package/src/components/grid/grid.js +4 -1
- package/src/components/message/message.css +36 -3
- package/src/components/select/select.css +17 -0
- package/src/components/select/select.js +105 -24
- package/src/components/table/table.css +85 -0
- package/src/components/table/table.js +418 -24
- package/src/components/tag-picker/tag-picker.css +19 -0
- package/src/components/tag-picker/tag-picker.js +42 -5
- package/src/core/dom.js +1 -1
- package/src/core/util.js +20 -0
- package/src/index.js +7 -1
- package/src/zeyos/icons.js +34 -21
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/select.js +6 -5
- package/styles/tokens/semantic.css +9 -0
- package/styles/tokens/themes.css +2 -2
- 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 |
|
|
37
|
-
| Editable billing/transaction rows | Covered at generic-grid level | `Grid.BillingItems()` composes typed
|
|
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 |
|
|
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>>} */
|