@zeyos/zx 3.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 +95 -0
- package/DESIGN-SYSTEM.md +87 -71
- package/README.md +16 -37
- package/dist/types/components/app-icon/app-icon.d.ts +88 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +106 -32
- package/dist/types/components/card/card.d.ts +128 -0
- package/dist/types/components/chart/chart.d.ts +1 -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/launcher/launcher.d.ts +65 -6
- package/dist/types/components/select/select.d.ts +97 -4
- package/dist/types/components/table/table.d.ts +107 -3
- package/dist/types/components/tag-picker/tag-picker.d.ts +16 -0
- package/dist/types/core/dom.d.ts +9 -1
- package/dist/types/core/util.d.ts +9 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/{components/app-rail → internal}/app-rail.d.ts +34 -19
- package/dist/types/zeyos/icons.d.ts +24 -2
- package/dist/types/zeyos/index.d.ts +3 -2
- package/dist/types/zeyos/launcher.d.ts +147 -0
- package/dist/types/zeyos/select.d.ts +41 -0
- package/dist/zx-zeyos.esm.js +2018 -113
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +815 -16
- package/dist/zx.esm.js +2661 -338
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +2661 -338
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +602 -110
- package/docs/llms.md +181 -123
- package/docs/llms.txt +24 -21
- package/package.json +8 -17
- package/src/components/app-icon/app-icon.css +105 -0
- package/src/components/app-icon/app-icon.js +134 -0
- package/src/components/app-sidebar/app-sidebar.css +12 -0
- package/src/components/app-sidebar/app-sidebar.js +310 -110
- package/src/components/card/card.css +149 -0
- package/src/components/card/card.js +285 -0
- package/src/components/chart/chart.js +8 -3
- 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 +14 -4
- package/src/components/launcher/launcher.css +113 -3
- package/src/components/launcher/launcher.js +234 -26
- package/src/components/message/message.css +36 -3
- package/src/components/select/field-adapter.js +9 -4
- package/src/components/select/select.css +161 -10
- package/src/components/select/select.js +361 -48
- package/src/components/table/table.css +85 -0
- package/src/components/table/table.js +426 -31
- 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 +21 -1
- package/src/core/util.js +20 -0
- package/src/index.js +9 -2
- package/src/{components/app-rail → internal}/app-rail.js +80 -30
- package/src/zeyos/icons.js +38 -20
- package/src/zeyos/index.js +8 -2
- package/src/zeyos/launcher.js +338 -0
- package/src/zeyos/modules.js +1 -0
- package/src/zeyos/select.js +116 -1
- package/styles/tokens/global.css +1 -1
- package/styles/tokens/modules.css +1 -0
- package/styles/tokens/semantic.css +9 -0
- package/styles/tokens/themes.css +2 -2
- package/styles/zx.css +3 -0
- package/MIGRATION.md +0 -749
- package/dist/types/compat/base.d.ts +0 -156
- package/dist/types/compat/globals.d.ts +0 -20
- package/dist/types/compat/index.d.ts +0 -128
- package/dist/types/compat/map/basic.d.ts +0 -175
- package/dist/types/compat/map/data.d.ts +0 -103
- package/dist/types/compat/map/date-time.d.ts +0 -80
- package/dist/types/compat/map/factory.d.ts +0 -9
- package/dist/types/compat/map/form.d.ts +0 -98
- package/dist/types/compat/map/helpers.d.ts +0 -12
- package/dist/types/compat/map/http.d.ts +0 -36
- package/dist/types/compat/map/layout.d.ts +0 -106
- package/dist/types/compat/map/options.d.ts +0 -80
- package/dist/types/compat/map/select.d.ts +0 -95
- package/dist/types/compat/map/stubs.d.ts +0 -32
- package/dist/types/compat/map/utilities.d.ts +0 -37
- package/dist/types/compat-entry.d.ts +0 -5
- package/dist/zx-compat.esm.js +0 -17848
- package/dist/zx-compat.esm.min.js +0 -1
- package/dist/zx-compat.global.js +0 -29008
- package/dist/zx-compat.global.min.js +0 -3
- package/src/compat/base.js +0 -291
- package/src/compat/globals.js +0 -167
- package/src/compat/index.js +0 -84
- package/src/compat/map/basic.js +0 -464
- package/src/compat/map/data.js +0 -254
- package/src/compat/map/date-time.js +0 -177
- package/src/compat/map/factory.js +0 -45
- package/src/compat/map/form.js +0 -330
- package/src/compat/map/helpers.js +0 -46
- package/src/compat/map/http.js +0 -117
- package/src/compat/map/layout.js +0 -256
- package/src/compat/map/options.js +0 -267
- package/src/compat/map/select.js +0 -198
- package/src/compat/map/stubs.js +0 -26
- package/src/compat/map/utilities.js +0 -113
- package/src/compat-entry.js +0 -11
|
@@ -47,6 +47,22 @@
|
|
|
47
47
|
line-height: 1.5;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
.zx-tag-picker__tag[data-colored="true"] {
|
|
51
|
+
border-color: color-mix(in srgb, var(--zx-tag-color) 48%, var(--zx-color-border));
|
|
52
|
+
background: color-mix(in srgb, var(--zx-tag-color) 14%, var(--zx-color-bg-muted));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.zx-tag-picker__visual {
|
|
56
|
+
display: inline-grid;
|
|
57
|
+
place-items: center;
|
|
58
|
+
flex: none;
|
|
59
|
+
inline-size: 18px;
|
|
60
|
+
block-size: 18px;
|
|
61
|
+
border-radius: var(--zx-radius-sm);
|
|
62
|
+
background: color-mix(in srgb, var(--zx-tag-color, var(--zx-color-accent)) 16%, transparent);
|
|
63
|
+
color: var(--zx-tag-color, var(--zx-color-accent));
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
.zx-tag-picker__tag:not(:has(.zx-tag-picker__remove)) {
|
|
51
67
|
padding-inline-end: var(--zx-space-2);
|
|
52
68
|
}
|
|
@@ -152,6 +168,9 @@
|
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
.zx-tag-picker__option-label {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: var(--zx-space-2);
|
|
155
174
|
overflow: hidden;
|
|
156
175
|
text-overflow: ellipsis;
|
|
157
176
|
white-space: nowrap;
|
|
@@ -2,7 +2,7 @@ import { Component } from '../../core/component.js';
|
|
|
2
2
|
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
4
|
import { position } from '../../core/position.js';
|
|
5
|
-
import { uid } from '../../core/util.js';
|
|
5
|
+
import { isCssColor, uid } from '../../core/util.js';
|
|
6
6
|
import { matchItems } from '../select/filter.js';
|
|
7
7
|
|
|
8
8
|
/** @typedef {Record<string, any>|string|number} TagItem */
|
|
@@ -14,6 +14,8 @@ import { matchItems } from '../select/filter.js';
|
|
|
14
14
|
* @property {unknown[]} [values=[]] Initially selected IDs.
|
|
15
15
|
* @property {TagValueReader} [valueKey='ID'] Item ID property or reader.
|
|
16
16
|
* @property {TagLabelReader} [labelKey='name'] Item label property or reader.
|
|
17
|
+
* @property {TagValueReader} [iconKey='icon'] Optional item icon property or reader.
|
|
18
|
+
* @property {TagValueReader} [colorKey='color'] Optional item colour property or reader.
|
|
17
19
|
* @property {Array<TagValueReader>|null} [searchKeys=null] Fields searched by local filtering.
|
|
18
20
|
* @property {'local'|((query: string) => Promise<TagItem[]>|TagItem[])} [filter='local'] Filtering mode.
|
|
19
21
|
* @property {number} [minQuery=0] Minimum async query length.
|
|
@@ -65,6 +67,8 @@ export class TagPicker extends Component {
|
|
|
65
67
|
values: [],
|
|
66
68
|
valueKey: 'ID',
|
|
67
69
|
labelKey: 'name',
|
|
70
|
+
iconKey: 'icon',
|
|
71
|
+
colorKey: 'color',
|
|
68
72
|
searchKeys: null,
|
|
69
73
|
filter: 'local',
|
|
70
74
|
minQuery: 0,
|
|
@@ -411,9 +415,14 @@ export class TagPicker extends Component {
|
|
|
411
415
|
_renderTags() {
|
|
412
416
|
const tags = this._selected.map((item) => {
|
|
413
417
|
const label = this._labelOf(item);
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
h('span', { class: 'zx-tag-picker__tag-label' },
|
|
418
|
+
const color = this._colorOf(item);
|
|
419
|
+
const content = this.options.renderTag ? this.options.renderTag(item)
|
|
420
|
+
: [this._iconOf(item), h('span', { class: 'zx-tag-picker__tag-label' }, label)];
|
|
421
|
+
const tag = h('span', {
|
|
422
|
+
class: 'zx-tag-picker__tag',
|
|
423
|
+
dataset: { colored: String(Boolean(color)) },
|
|
424
|
+
style: color ? { '--zx-tag-color': color } : null
|
|
425
|
+
}, content);
|
|
417
426
|
if (!this._readonly && !this._disabled) {
|
|
418
427
|
tag.append(h('button', {
|
|
419
428
|
class: 'zx-icon-btn zx-tag-picker__remove',
|
|
@@ -446,7 +455,8 @@ export class TagPicker extends Component {
|
|
|
446
455
|
h('span', { class: 'zx-tag-picker__check', ariaHidden: 'true' },
|
|
447
456
|
selected ? icon('check', { size: 13 }) : null),
|
|
448
457
|
h('span', { class: 'zx-tag-picker__option-label' },
|
|
449
|
-
this.options.renderItem ? this.options.renderItem(item)
|
|
458
|
+
this.options.renderItem ? this.options.renderItem(item)
|
|
459
|
+
: [this._iconOf(item), h('span', {}, this._labelOf(item))]));
|
|
450
460
|
if (!selected && this.isFull()) option.setAttribute('aria-disabled', 'true');
|
|
451
461
|
this._entries.push({ item, element: option, create: false });
|
|
452
462
|
nodes.push(option);
|
|
@@ -773,6 +783,33 @@ export class TagPicker extends Component {
|
|
|
773
783
|
return String(item ?? '');
|
|
774
784
|
}
|
|
775
785
|
|
|
786
|
+
/** @param {unknown} item @returns {Node|null} */
|
|
787
|
+
_iconOf(item) {
|
|
788
|
+
const value = this._readItem(item, this.options.iconKey);
|
|
789
|
+
const visual = value && typeof value === 'object' && typeof value.nodeType === 'number'
|
|
790
|
+
? /** @type {Node} */ (value).cloneNode(true)
|
|
791
|
+
: typeof value === 'string' && value.trim() ? icon(value, { size: 12 }) : null;
|
|
792
|
+
if (!visual) return null;
|
|
793
|
+
return h('span', {
|
|
794
|
+
class: 'zx-tag-picker__visual',
|
|
795
|
+
style: this._colorOf(item) ? { '--zx-tag-color': this._colorOf(item) } : null,
|
|
796
|
+
ariaHidden: 'true'
|
|
797
|
+
}, visual);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** @param {unknown} item @returns {string|null} */
|
|
801
|
+
_colorOf(item) {
|
|
802
|
+
const value = this._readItem(item, this.options.colorKey);
|
|
803
|
+
return isCssColor(value) ? value : null;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/** @param {unknown} item @param {unknown} reader @returns {unknown} */
|
|
807
|
+
_readItem(item, reader) {
|
|
808
|
+
if (typeof reader === 'function') return reader(/** @type {TagItem} */ (item));
|
|
809
|
+
if (item !== null && typeof item === 'object' && typeof reader === 'string') return item[reader];
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
|
|
776
813
|
/** @returns {void} */
|
|
777
814
|
_emitChange() {
|
|
778
815
|
this.emit('change', { values: this.getValues(), items: this.getItems() });
|
package/src/core/dom.js
CHANGED
|
@@ -92,9 +92,29 @@ export function compactElement(compact, properties = null, content = null) {
|
|
|
92
92
|
return element;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/**
|
|
95
|
+
/** Public compact-builder alias for concise application DOM construction. */
|
|
96
96
|
export const __ = compactElement;
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Returns a trimmed native-link destination unless its parsed scheme can execute document code.
|
|
100
|
+
* Relative URLs, fragments, and ordinary application protocols remain valid; malformed and
|
|
101
|
+
* control-obfuscated script/data schemes are rejected.
|
|
102
|
+
* @param {unknown} value Candidate href.
|
|
103
|
+
* @returns {string|null}
|
|
104
|
+
*/
|
|
105
|
+
export function safeHref(value) {
|
|
106
|
+
if (typeof value !== 'string') return null;
|
|
107
|
+
const href = value.trim();
|
|
108
|
+
if (!href) return null;
|
|
109
|
+
try {
|
|
110
|
+
const protocol = new URL(href, globalThis.location?.href ?? 'https://zx.invalid/').protocol;
|
|
111
|
+
if (['javascript:', 'data:', 'vbscript:'].includes(protocol.toLowerCase())) return null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return href;
|
|
116
|
+
}
|
|
117
|
+
|
|
98
118
|
/**
|
|
99
119
|
* Applies the established ZeyOS property rules to an existing element:
|
|
100
120
|
* `Dname` → `data-name`, `SfontSize` → `style.fontSize`, `on*` → a legacy-compatible listener,
|
package/src/core/util.js
CHANGED
|
@@ -122,6 +122,26 @@ export function isElement(value) {
|
|
|
122
122
|
&& /** @type {{nodeType?: unknown}} */ (value).nodeType === 1);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Accepts concrete CSS colours while rejecting image/function values that could make a colour
|
|
127
|
+
* descriptor trigger a network request when it is later consumed through a custom property.
|
|
128
|
+
* `CSS.supports()` provides the authoritative browser check; the conservative fallback covers
|
|
129
|
+
* ordinary named, hexadecimal, and functional colours in non-browser test environments.
|
|
130
|
+
* @param {unknown} value Candidate colour.
|
|
131
|
+
* @returns {value is string}
|
|
132
|
+
*/
|
|
133
|
+
export function isCssColor(value) {
|
|
134
|
+
if (typeof value !== 'string') return false;
|
|
135
|
+
const text = value.trim();
|
|
136
|
+
if (!text || text.length > 256 || /[;{}\u0000-\u001f\u007f]/.test(text)
|
|
137
|
+
|| /(?:url|image|image-set|cross-fade|element|var)\s*\(/i.test(text)) return false;
|
|
138
|
+
const supports = globalThis.CSS?.supports;
|
|
139
|
+
if (typeof supports === 'function') {
|
|
140
|
+
try { return supports.call(globalThis.CSS, 'color', text); } catch { return false; }
|
|
141
|
+
}
|
|
142
|
+
return /^(?:#[\da-f]{3,8}|[a-z]+|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix)\([^;{}]+\))$/i.test(text);
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
/**
|
|
126
146
|
* Restricts a number to an inclusive range.
|
|
127
147
|
* @param {number} n Value to restrict.
|
package/src/index.js
CHANGED
|
@@ -88,6 +88,7 @@ export { CopyInput } from './components/copy/copy-input.js';
|
|
|
88
88
|
export { Toolbar } from './components/toolbar/toolbar.js';
|
|
89
89
|
export { Toggle } from './components/toggle/toggle.js';
|
|
90
90
|
export { Groupbox } from './components/groupbox/groupbox.js';
|
|
91
|
+
export { Card } from './components/card/card.js';
|
|
91
92
|
export { Panel } from './components/panel/panel.js';
|
|
92
93
|
export { MasterPanel } from './components/master-panel/master-panel.js';
|
|
93
94
|
export {
|
|
@@ -100,10 +101,11 @@ export { Stepper } from './components/stepper/stepper.js';
|
|
|
100
101
|
export { Breadcrumb } from './components/breadcrumb/breadcrumb.js';
|
|
101
102
|
export { Pagination, paginationRange } from './components/pagination/pagination.js';
|
|
102
103
|
export { Search } from './components/search/search.js';
|
|
104
|
+
export { AppIcon, appIcon } from './components/app-icon/app-icon.js';
|
|
103
105
|
export { Launcher, rankLauncherItems } from './components/launcher/launcher.js';
|
|
104
106
|
export { Avatar, avatarInitials } from './components/avatar/avatar.js';
|
|
105
107
|
export { AccountMenu } from './components/account-menu/account-menu.js';
|
|
106
|
-
export {
|
|
108
|
+
export { normalizeAppItems } from './internal/app-rail.js';
|
|
107
109
|
export { AppSidebar } from './components/app-sidebar/app-sidebar.js';
|
|
108
110
|
export { Message } from './components/message/message.js';
|
|
109
111
|
export { Modal } from './components/modal/modal.js';
|
|
@@ -131,7 +133,7 @@ export {
|
|
|
131
133
|
DateRangeBox, formatRangeText, parseRangeText
|
|
132
134
|
} from './components/datebox/date-range-box.js';
|
|
133
135
|
export { Timebox, splitTime, joinTime } from './components/timebox/timebox.js';
|
|
134
|
-
export { Table } from './components/table/table.js';
|
|
136
|
+
export { Table, reorderTableRows } from './components/table/table.js';
|
|
135
137
|
export { Grid, billingColumns, isBillingLine } from './components/grid/grid.js';
|
|
136
138
|
export {
|
|
137
139
|
Chart, ChartJsAdapter, chartSummaryTable, cloneChartValue, isEmptyChartData
|
|
@@ -139,6 +141,11 @@ export {
|
|
|
139
141
|
export { TreeView } from './components/tree/tree.js';
|
|
140
142
|
export { Finder } from './components/finder/finder.js';
|
|
141
143
|
export { DataFilter } from './components/data-filter/data-filter.js';
|
|
144
|
+
export { Filter } from './components/filter/filter.js';
|
|
145
|
+
export {
|
|
146
|
+
filterOperators, emptyFilterAst, filterCondition, filterGroup,
|
|
147
|
+
parseFilterAst, cloneFilterAst, stringifyFilterAst, validateFilterAst
|
|
148
|
+
} from './components/filter/filter-model.js';
|
|
142
149
|
export { Field } from './components/field/field.js';
|
|
143
150
|
export { Fieldset } from './components/fieldset/fieldset.js';
|
|
144
151
|
export { Form } from './components/form/form.js';
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { Component } from '
|
|
2
|
-
import { h } from '
|
|
3
|
-
import { icon } from '
|
|
4
|
-
import { Dropdown } from '../dropdown/dropdown.js';
|
|
5
|
-
import { Tooltip } from '../tooltip/tooltip.js';
|
|
1
|
+
import { Component } from '../core/component.js';
|
|
2
|
+
import { h, safeHref } from '../core/dom.js';
|
|
3
|
+
import { icon } from '../core/icons.js';
|
|
4
|
+
import { Dropdown } from '../components/dropdown/dropdown.js';
|
|
5
|
+
import { Tooltip } from '../components/tooltip/tooltip.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {Object} AppNavItem
|
|
9
9
|
* @property {string|number} id Stable item identifier.
|
|
10
10
|
* @property {string} label Visible/accessibility label.
|
|
11
|
-
* @property {string} [icon]
|
|
11
|
+
* @property {string|Node|{toElement: () => Node|null}|((context: Record<string, any>, item: AppNavItem) => Node|null)} [icon] Icon name, node, component, or factory.
|
|
12
12
|
* @property {string|number} [badge] Count or status badge.
|
|
13
13
|
* @property {unknown} [value] Emitted value; defaults to `id`.
|
|
14
|
-
* @property {string} [href] Native navigation target.
|
|
14
|
+
* @property {string} [href] Native navigation target; executable/data schemes are disabled.
|
|
15
15
|
* @property {string} [target] Native link browsing context.
|
|
16
16
|
* @property {() => void} [invoke] Application-owned non-link action.
|
|
17
17
|
* @property {boolean} [disabled] Whether activation is unavailable.
|
|
18
18
|
* @property {AppNavItem[]} [children] Child destinations/actions.
|
|
19
19
|
*/
|
|
20
20
|
/**
|
|
21
|
-
* @typedef {Object}
|
|
21
|
+
* @typedef {Object} AppRailPresenterOptions
|
|
22
22
|
* @property {AppNavItem[]} [items=[]] Application navigation tree.
|
|
23
23
|
* @property {string|number|null} [active=null] Active item id.
|
|
24
24
|
* @property {'vertical'|'horizontal'} [orientation='vertical'] Rail flow.
|
|
@@ -28,6 +28,7 @@ import { Tooltip } from '../tooltip/tooltip.js';
|
|
|
28
28
|
* @property {Node|string|number|{toElement: () => Node|null}|null} [footer=null] Trailing rail content.
|
|
29
29
|
* @property {number} [openDelay=80] Hover-open delay in milliseconds.
|
|
30
30
|
* @property {number} [closeDelay=160] Pointer/focus crossing grace in milliseconds.
|
|
31
|
+
* @property {(item: AppNavItem, context: Record<string, any>) => Node|null} [renderIcon] Optional host icon renderer.
|
|
31
32
|
* @property {(event: CustomEvent<AppNavSelectDetail>) => void} [onselect] Preventable action listener.
|
|
32
33
|
* @property {(event: CustomEvent<{item: AppNavItem, id: string|number, open: boolean}>) => void} [onflyoutchange] Flyout-state listener.
|
|
33
34
|
*/
|
|
@@ -36,14 +37,14 @@ import { Tooltip } from '../tooltip/tooltip.js';
|
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Minimized application navigation; descendants always open in hover/focus flyouts, never inline.
|
|
39
|
-
* @fires
|
|
40
|
-
* @fires
|
|
41
|
-
* @extends {Component<
|
|
40
|
+
* @fires AppRailPresenter#select
|
|
41
|
+
* @fires AppRailPresenter#flyoutchange
|
|
42
|
+
* @extends {Component<AppRailPresenterOptions>}
|
|
42
43
|
*/
|
|
43
|
-
export class
|
|
44
|
+
export class AppRailPresenter extends Component {
|
|
44
45
|
static cssName = 'app-rail';
|
|
45
46
|
|
|
46
|
-
/** @type {Readonly<
|
|
47
|
+
/** @type {Readonly<AppRailPresenterOptions>} */
|
|
47
48
|
static defaults = {
|
|
48
49
|
items: [],
|
|
49
50
|
active: null,
|
|
@@ -53,13 +54,14 @@ export class AppRail extends Component {
|
|
|
53
54
|
header: null,
|
|
54
55
|
footer: null,
|
|
55
56
|
openDelay: 80,
|
|
56
|
-
closeDelay: 160
|
|
57
|
+
closeDelay: 160,
|
|
58
|
+
renderIcon: null
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* Creates or enhances a rail host.
|
|
61
63
|
* @param {Element|string|null} target Rail target.
|
|
62
|
-
* @param {
|
|
64
|
+
* @param {AppRailPresenterOptions} [options={}] Rail options.
|
|
63
65
|
*/
|
|
64
66
|
constructor(target = null, options = {}) {
|
|
65
67
|
super(target, options);
|
|
@@ -213,7 +215,14 @@ export class AppRail extends Component {
|
|
|
213
215
|
index: String(item.id),
|
|
214
216
|
iconOnly: true,
|
|
215
217
|
disclosure: hasChildren,
|
|
216
|
-
disclosureIcon: railDisclosureIcon(this.el.dataset.side)
|
|
218
|
+
disclosureIcon: railDisclosureIcon(this.el.dataset.side),
|
|
219
|
+
visual: appNavIcon(item, this.options.renderIcon, {
|
|
220
|
+
orientation: this.el.dataset.orientation,
|
|
221
|
+
collapsed: true,
|
|
222
|
+
side: this.el.dataset.side,
|
|
223
|
+
level: 1,
|
|
224
|
+
location: 'root'
|
|
225
|
+
})
|
|
217
226
|
});
|
|
218
227
|
const row = h('li', { class: 'zx-app-rail__row' }, control);
|
|
219
228
|
this.refs.list.append(row);
|
|
@@ -255,7 +264,14 @@ export class AppRail extends Component {
|
|
|
255
264
|
index: childKey,
|
|
256
265
|
iconOnly: false,
|
|
257
266
|
disclosure: hasChildren,
|
|
258
|
-
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side)
|
|
267
|
+
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side),
|
|
268
|
+
visual: appNavIcon(child, this.options.renderIcon, {
|
|
269
|
+
orientation: this.el.dataset.orientation,
|
|
270
|
+
collapsed: true,
|
|
271
|
+
side: this.el.dataset.side,
|
|
272
|
+
level: childKey.split('/').length,
|
|
273
|
+
location: 'flyout'
|
|
274
|
+
})
|
|
259
275
|
});
|
|
260
276
|
list.append(h('li', {}, control));
|
|
261
277
|
if (hasChildren) this._buildFlyout(child, control, childKey, key);
|
|
@@ -305,7 +321,6 @@ export class AppRail extends Component {
|
|
|
305
321
|
this._interaction.listen(trigger, 'click', (event) => {
|
|
306
322
|
event.preventDefault();
|
|
307
323
|
if (event instanceof MouseEvent && event.detail === 0) this.openFlyout(key, { focus: true });
|
|
308
|
-
else if (dropdown.isOpen()) this.closeFlyout(key);
|
|
309
324
|
else this.openFlyout(key);
|
|
310
325
|
});
|
|
311
326
|
this._interaction.listen(actualPanel, 'pointerenter', () => this._cancelFlyoutTimers(flyout, true));
|
|
@@ -582,26 +597,61 @@ class RailListenerScope extends Component {
|
|
|
582
597
|
export function normalizeAppItems(items) {
|
|
583
598
|
if (!Array.isArray(items)) return [];
|
|
584
599
|
return items.filter((item) => item && typeof item === 'object' && item.id != null && item.label != null)
|
|
585
|
-
.map((item) =>
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
600
|
+
.map((item) => {
|
|
601
|
+
const normalized = {
|
|
602
|
+
...item,
|
|
603
|
+
label: String(item.label),
|
|
604
|
+
children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
|
|
605
|
+
};
|
|
606
|
+
if (item.href == null) return normalized;
|
|
607
|
+
const href = safeHref(item.href);
|
|
608
|
+
if (href !== null) normalized.href = href;
|
|
609
|
+
else {
|
|
610
|
+
delete normalized.href;
|
|
611
|
+
if (normalized.children.length === 0 && typeof normalized.invoke !== 'function') {
|
|
612
|
+
normalized.disabled = true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return normalized;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Resolves a host-rendered or item-owned navigation icon without moving a mounted source node.
|
|
621
|
+
* @param {AppNavItem} item Navigation item.
|
|
622
|
+
* @param {unknown} renderer Optional component-level renderer.
|
|
623
|
+
* @param {Record<string, any>} context Presentation context.
|
|
624
|
+
* @returns {Node|null}
|
|
625
|
+
*/
|
|
626
|
+
export function appNavIcon(item, renderer, context) {
|
|
627
|
+
let visual = typeof renderer === 'function' ? renderer(item, context) : null;
|
|
628
|
+
if (visual == null) visual = item.icon;
|
|
629
|
+
if (typeof visual === 'function') visual = visual(context, item);
|
|
630
|
+
if (typeof visual === 'string') return icon(visual);
|
|
631
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
632
|
+
if (visual && typeof visual === 'object'
|
|
633
|
+
&& typeof (/** @type {any} */ (visual)).toElement === 'function') {
|
|
634
|
+
const element = (/** @type {any} */ (visual)).toElement();
|
|
635
|
+
return element instanceof Node ? element.cloneNode(true) : null;
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
590
638
|
}
|
|
591
639
|
|
|
592
|
-
/** @param {AppNavItem} item @param {{className: string, index: string, iconOnly: boolean, disclosure: boolean, disclosureIcon: string}} options @returns {HTMLElement} */
|
|
593
|
-
function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon }) {
|
|
594
|
-
const
|
|
640
|
+
/** @param {AppNavItem} item @param {{className: string, index: string, iconOnly: boolean, disclosure: boolean, disclosureIcon: string, visual: Node|null}} options @returns {HTMLElement} */
|
|
641
|
+
function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon, visual }) {
|
|
642
|
+
const href = safeHref(item.href);
|
|
643
|
+
const tag = href && !disclosure ? 'a' : 'button';
|
|
595
644
|
return /** @type {HTMLElement} */ (h(tag, {
|
|
596
645
|
class: className,
|
|
597
646
|
type: tag === 'button' ? 'button' : null,
|
|
598
|
-
href: tag === 'a' ?
|
|
647
|
+
href: tag === 'a' ? href : null,
|
|
599
648
|
target: tag === 'a' ? item.target ?? null : null,
|
|
649
|
+
rel: tag === 'a' ? 'noopener' : null,
|
|
600
650
|
ariaLabel: iconOnly ? item.label : null,
|
|
601
651
|
ariaDisabled: item.disabled ? 'true' : null,
|
|
602
652
|
dataset: { appNavId: index, appNavValueId: String(item.id) }
|
|
603
653
|
},
|
|
604
|
-
|
|
654
|
+
visual ? h('span', { class: `${className}-icon`, ariaHidden: 'true' }, visual) : null,
|
|
605
655
|
h('span', { class: `${className}-label` }, item.label),
|
|
606
656
|
item.badge != null ? h('span', { class: `${className}-badge` }, String(item.badge)) : null,
|
|
607
657
|
disclosure ? h('span', { class: `${className}-disclosure`, ariaHidden: 'true' }, icon(disclosureIcon)) : null));
|
|
@@ -664,5 +714,5 @@ function appendSlot(target, content) {
|
|
|
664
714
|
} else if (typeof content === 'object' && typeof content.nodeType === 'number') target.append(content);
|
|
665
715
|
}
|
|
666
716
|
|
|
667
|
-
/** Fired when a destination/action is activated. @event
|
|
668
|
-
/** Fired when a flyout opens or closes. @event
|
|
717
|
+
/** Fired when a destination/action is activated. @event AppRailPresenter#select @type {CustomEvent<AppNavSelectDetail>} */
|
|
718
|
+
/** Fired when a flyout opens or closes. @event AppRailPresenter#flyoutchange @type {CustomEvent<{item: AppNavItem, id: string|number, open: boolean}>} */
|
package/src/zeyos/icons.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* @module zx/zeyos/icons
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { icon, loadFontAwesome } from '../index.js';
|
|
8
|
+
import { appIcon } from '../components/app-icon/app-icon.js';
|
|
9
|
+
import { isCssColor } from '../core/util.js';
|
|
8
10
|
import { moduleGlyphColor, moduleInfo, normalizeModuleName } from './modules.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -51,10 +53,13 @@ export function moduleIcon(name, options = {}) {
|
|
|
51
53
|
/**
|
|
52
54
|
* @typedef {Object} ModuleChipOptions
|
|
53
55
|
* @property {number|string} [size=24] Chip edge length.
|
|
54
|
-
* @property {number|string} [iconSize='
|
|
56
|
+
* @property {number|string} [iconSize='52%'] Glyph size relative to the tile edge.
|
|
55
57
|
* @property {string|null} [label=null] Accessible label; null marks the chip decorative.
|
|
56
58
|
* @property {boolean} [title=false] Adds the module's display name as a `title` tooltip.
|
|
57
59
|
* @property {boolean} [standard=false] Use the stock Font Awesome fallback glyph.
|
|
60
|
+
* @property {string|Node|(()=>string|Node)|null} [icon] Explicit glyph override, useful for an offline fallback.
|
|
61
|
+
* @property {string} [color] Override the module colour for a server-defined fork or weblet.
|
|
62
|
+
* @property {false|'subtle'|'strong'} [glass='subtle'] AppIcon material treatment.
|
|
58
63
|
* @property {string|string[]} [class] Extra class names.
|
|
59
64
|
*/
|
|
60
65
|
|
|
@@ -67,26 +72,39 @@ export function moduleIcon(name, options = {}) {
|
|
|
67
72
|
* @returns {HTMLElement} A `<span class="zx-module-icon">` wrapping the glyph.
|
|
68
73
|
*/
|
|
69
74
|
export function moduleChip(name, options = {}) {
|
|
70
|
-
const {
|
|
75
|
+
const {
|
|
76
|
+
size = 24, iconSize = '52%', label = null, title = false, standard = false,
|
|
77
|
+
color = null
|
|
78
|
+
} = options;
|
|
71
79
|
const key = normalizeModuleName(name);
|
|
72
80
|
const info = moduleInfo(name);
|
|
81
|
+
const background = isCssColor(color) ? color : info.color;
|
|
73
82
|
|
|
74
|
-
const chip =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
83
|
+
const chip = appIcon({
|
|
84
|
+
// AppIcon owns glyph sizing. Keep the provider glyph at 1em so the percentage is not applied
|
|
85
|
+
// once to the wrapper and a second time to a Font Awesome <i>.
|
|
86
|
+
icon: options.icon ?? moduleIcon(name, { size: '1em', standard }),
|
|
87
|
+
size,
|
|
88
|
+
iconSize,
|
|
89
|
+
color: background,
|
|
90
|
+
label,
|
|
91
|
+
glass: options.glass ?? 'subtle',
|
|
92
|
+
class: ['zx-module-icon', options.class].flat().filter(Boolean)
|
|
93
|
+
});
|
|
94
|
+
chip.dataset.module = key;
|
|
95
|
+
chip.style.setProperty('--zx-module-color', background);
|
|
96
|
+
chip.style.setProperty('--zx-module-glyph', moduleGlyphColor(background));
|
|
97
|
+
chip.style.setProperty('--zx-app-icon-glyph', moduleGlyphColor(background));
|
|
98
|
+
if (title) chip.title = info.label;
|
|
91
99
|
return chip;
|
|
92
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates the public ZeyOS AppIcon preset for any module, entity, fork, or weblet identity.
|
|
104
|
+
* @param {string} name Module/entity identifier.
|
|
105
|
+
* @param {ModuleChipOptions} [options={}] AppIcon options.
|
|
106
|
+
* @returns {HTMLElement}
|
|
107
|
+
*/
|
|
108
|
+
export function zeyosAppIcon(name, options = {}) {
|
|
109
|
+
return moduleChip(name, options);
|
|
110
|
+
}
|
package/src/zeyos/index.js
CHANGED
|
@@ -15,7 +15,13 @@ export {
|
|
|
15
15
|
zeyosModules, moduleAliases, moduleInfo, moduleColor, moduleIconName, moduleKeys,
|
|
16
16
|
moduleGlyphColor, normalizeModuleName, registerModules
|
|
17
17
|
} from './modules.js';
|
|
18
|
-
export { ZEYOS_ICON_KIT, useZeyosIcons, moduleIcon, moduleChip } from './icons.js';
|
|
19
|
-
export {
|
|
18
|
+
export { ZEYOS_ICON_KIT, useZeyosIcons, moduleIcon, moduleChip, zeyosAppIcon } from './icons.js';
|
|
19
|
+
export {
|
|
20
|
+
ZEYOS_LAUNCHER_APPLICATIONS, buildZeyosLauncherConfig, zeyosLauncher,
|
|
21
|
+
normalizeZeyosLauncherApplications, normalizeZeyosLauncherRecords, zeyosActiveIdentifier
|
|
22
|
+
} from './launcher.js';
|
|
23
|
+
export {
|
|
24
|
+
zeyosSelect, buildZeyosSelectConfig, zeyosEntitySelect, buildZeyosEntitySelectConfig
|
|
25
|
+
} from './select.js';
|
|
20
26
|
export { zeyosForm } from './form.js';
|
|
21
27
|
export { zeyosTable } from './table.js';
|