@zeyos/zx 4.4.0 → 5.2.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 +447 -0
- package/README.md +44 -11
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +31 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +15 -0
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +6 -17
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +24 -9
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +54 -2
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +26 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +268 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
- package/dist/types/components/menu-button/menu-button.d.ts +39 -5
- package/dist/types/components/message/message.d.ts +132 -17
- package/dist/types/components/modal/modal.d.ts +43 -2
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +17 -3
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +311 -18
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +42 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +110 -5
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +345 -22
- package/dist/types/zeyos/view.d.ts +26 -2
- package/dist/zx-zeyos.esm.js +6178 -1008
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +2861 -827
- package/dist/zx.esm.js +8909 -1480
- package/dist/zx.esm.min.js +55 -7
- package/dist/zx.global.js +8911 -1482
- package/dist/zx.global.min.js +55 -7
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1079 -117
- package/docs/llms.md +956 -154
- package/docs/llms.txt +9 -5
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +61 -1
- package/src/components/app-icon/app-icon.css +14 -3
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +59 -79
- package/src/components/app-sidebar/app-sidebar.css +304 -168
- package/src/components/app-sidebar/app-sidebar.js +667 -46
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/breadcrumb/breadcrumb.css +1 -1
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +47 -47
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +4 -4
- package/src/components/card-view/card-view.css +14 -12
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +7 -6
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +9 -4
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +3 -3
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +99 -17
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +159 -72
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +101 -29
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +38 -64
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +307 -11
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +16 -14
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +11 -3
- package/src/components/kanban-view/kanban-view.css +42 -18
- package/src/components/kanban-view/kanban-view.js +9 -6
- package/src/components/launcher/launcher.css +108 -26
- package/src/components/launcher/launcher.js +530 -42
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +40 -14
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +72 -4
- package/src/components/menu-button/menu-button.js +160 -18
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +264 -22
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +83 -15
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +3 -1
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +13 -11
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +4 -3
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +8 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +92 -47
- package/src/components/sheet/sheet.css +12 -9
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +18 -4
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +42 -47
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +41 -32
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +187 -37
- package/src/components/table/table.js +718 -73
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +44 -19
- package/src/components/tree/tree.css +26 -17
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/breakpoint.js +1 -1
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +204 -61
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +77 -3
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +10 -5
- package/src/internal/app-rail.js +135 -55
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +69 -27
- package/src/zeyos/index.js +4 -1
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +197 -13
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +287 -84
- package/src/zeyos/view.js +49 -93
- package/styles/base.css +72 -1
- package/styles/icons.css +11 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +117 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +140 -43
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +3 -1
- package/styles/tokens/density.css +0 -9
|
@@ -74,6 +74,11 @@ const FLOAT_STEP = 0.01;
|
|
|
74
74
|
* applies every committed edit.
|
|
75
75
|
* @property {FilterPanelSerialize} [serialize='seconds'] How a date leaves the component. A range's
|
|
76
76
|
* lower bound takes the start of its day and its upper bound the end, so a one-day range matches.
|
|
77
|
+
* @property {string|null} [timeZone=null] IANA zone the calendar days are counted in, such as the
|
|
78
|
+
* ZeyOS instance's `Europe/Vienna`. A day serializes as the first (or last) instant of that day
|
|
79
|
+
* in this zone, and an incoming instant shows as the day it falls on there — the same rule
|
|
80
|
+
* `zeyosForm()` and `zeyosTable()` apply to date-only fields. Null uses the runtime's own zone.
|
|
81
|
+
* An unknown zone throws a `RangeError`.
|
|
77
82
|
* @property {string|null} [emptyText=null] Text shown when `fields` is empty; null resolves
|
|
78
83
|
* `filterPanel.empty`.
|
|
79
84
|
* @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
|
|
@@ -111,6 +116,7 @@ export class FilterPanel extends Component {
|
|
|
111
116
|
value: {},
|
|
112
117
|
mode: 'draft',
|
|
113
118
|
serialize: 'seconds',
|
|
119
|
+
timeZone: null,
|
|
114
120
|
emptyText: null
|
|
115
121
|
};
|
|
116
122
|
|
|
@@ -130,9 +136,10 @@ export class FilterPanel extends Component {
|
|
|
130
136
|
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
131
137
|
this.el = root;
|
|
132
138
|
|
|
139
|
+
this._timeZone = normalizePanelTimeZone(this.options.timeZone);
|
|
133
140
|
this._fields = orderFields(this.options.fields, this.options.order);
|
|
134
|
-
this._draft = toDraft(this.options.value, this.options.fields);
|
|
135
|
-
this._applied = serializeFilterValue(this._draft, this.options.fields, this.options.serialize);
|
|
141
|
+
this._draft = toDraft(this.options.value, this.options.fields, this._timeZone);
|
|
142
|
+
this._applied = serializeFilterValue(this._draft, this.options.fields, this.options.serialize, this._timeZone);
|
|
136
143
|
this._appliedDraft = draftKey(this._draft);
|
|
137
144
|
|
|
138
145
|
const children = [];
|
|
@@ -187,7 +194,7 @@ export class FilterPanel extends Component {
|
|
|
187
194
|
* @fires FilterPanel#change
|
|
188
195
|
*/
|
|
189
196
|
setValue(value) {
|
|
190
|
-
this._draft = toDraft(value, this.options.fields);
|
|
197
|
+
this._draft = toDraft(value, this.options.fields, this._timeZone);
|
|
191
198
|
this._commit(this._draftValue());
|
|
192
199
|
this._writeControls();
|
|
193
200
|
this._syncActions();
|
|
@@ -219,7 +226,7 @@ export class FilterPanel extends Component {
|
|
|
219
226
|
* @fires FilterPanel#apply
|
|
220
227
|
*/
|
|
221
228
|
reset() {
|
|
222
|
-
return this._replaceDraft(toDraft(this.options.value, this.options.fields));
|
|
229
|
+
return this._replaceDraft(toDraft(this.options.value, this.options.fields, this._timeZone));
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
/**
|
|
@@ -233,7 +240,7 @@ export class FilterPanel extends Component {
|
|
|
233
240
|
* @fires FilterPanel#apply
|
|
234
241
|
*/
|
|
235
242
|
clear() {
|
|
236
|
-
return this._replaceDraft(toDraft({}, this.options.fields), { commit: true });
|
|
243
|
+
return this._replaceDraft(toDraft({}, this.options.fields, this._timeZone), { commit: true });
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
/** Destroys the composed controls and puts an enhanced target back as found. @returns {void} */
|
|
@@ -584,7 +591,7 @@ export class FilterPanel extends Component {
|
|
|
584
591
|
|
|
585
592
|
/** @returns {Record<string, unknown>} The draft, serialized and pruned. */
|
|
586
593
|
_draftValue() {
|
|
587
|
-
return serializeFilterValue(this._draft, this.options.fields, this.options.serialize);
|
|
594
|
+
return serializeFilterValue(this._draft, this.options.fields, this.options.serialize, this._timeZone);
|
|
588
595
|
}
|
|
589
596
|
|
|
590
597
|
/** Pushes the draft back into the controls without re-entering the edit path. @returns {void} */
|
|
@@ -703,42 +710,48 @@ export function pruneValue(value) {
|
|
|
703
710
|
|
|
704
711
|
/**
|
|
705
712
|
* Converts anything a calendar day can arrive as — a `YYYY-MM-DD` string, an ISO timestamp, a
|
|
706
|
-
* `Date`, Unix seconds or milliseconds — into the
|
|
713
|
+
* `Date`, Unix seconds or milliseconds — into the calendar day a native date input holds.
|
|
707
714
|
* Numbers below 1e11 are read as seconds, which is the boundary between the two epochs in use.
|
|
715
|
+
* An instant — Unix seconds or milliseconds, an ISO timestamp — names the day it falls on in
|
|
716
|
+
* `timeZone`. A `Date` is read as a picked day and keeps its local calendar day, matching
|
|
717
|
+
* `calendarDayToUnixSeconds()` in the ZeyOS binding.
|
|
708
718
|
* @param {unknown} value Candidate day.
|
|
719
|
+
* @param {string|null} [timeZone=null] IANA zone instants are read in; null for the runtime's own.
|
|
709
720
|
* @returns {string} `YYYY-MM-DD`, or an empty string when there is no day in it.
|
|
710
721
|
*/
|
|
711
|
-
export function toDateInput(value) {
|
|
722
|
+
export function toDateInput(value, timeZone = null) {
|
|
712
723
|
if (value === null || value === undefined || value === '') return '';
|
|
713
|
-
|
|
724
|
+
// A Date is a picked day — what Datebox and DatePicker hold — so its local day counts, as in
|
|
725
|
+
// `calendarDayToUnixSeconds()`; numbers and timestamps are instants, read in the zone.
|
|
726
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? '' : zoneDay(value.getTime(), null);
|
|
714
727
|
if (typeof value === 'number') {
|
|
715
728
|
if (!Number.isFinite(value)) return '';
|
|
716
|
-
return
|
|
729
|
+
return zoneDay(Math.abs(value) < SECONDS_CUTOFF ? value * 1000 : value, timeZone);
|
|
717
730
|
}
|
|
718
731
|
const text = String(value).trim();
|
|
719
732
|
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) return text;
|
|
720
|
-
if (/^-?\d+(\.\d+)?$/.test(text)) return toDateInput(Number(text));
|
|
733
|
+
if (/^-?\d+(\.\d+)?$/.test(text)) return toDateInput(Number(text), timeZone);
|
|
721
734
|
const parsed = new Date(text);
|
|
722
|
-
return Number.isNaN(parsed.getTime()) ? '' :
|
|
735
|
+
return Number.isNaN(parsed.getTime()) ? '' : zoneDay(parsed.getTime(), timeZone);
|
|
723
736
|
}
|
|
724
737
|
|
|
725
738
|
/**
|
|
726
739
|
* Serializes one calendar day at one edge of a range. The `from` edge takes the first instant of
|
|
727
|
-
* its
|
|
728
|
-
* everything recorded on it.
|
|
740
|
+
* its day and the `to` edge the last, so a range whose ends name the same day still matches
|
|
741
|
+
* everything recorded on it. The day is counted in `timeZone`: with `Europe/Vienna`, 2026-03-10
|
|
742
|
+
* starts at 23:00 UTC on the 9th whatever zone the browser is in.
|
|
729
743
|
* @param {unknown} day Calendar day in any form `toDateInput()` accepts.
|
|
730
744
|
* @param {FilterPanelEdge} edge Which end of the range the day is.
|
|
731
745
|
* @param {FilterPanelSerialize} [serialize='seconds'] Output format, or a function receiving the
|
|
732
746
|
* edge instant as a `Date`.
|
|
747
|
+
* @param {string|null} [timeZone=null] IANA zone the day is counted in; null for the runtime's own.
|
|
733
748
|
* @returns {unknown} The serialized day, or null when there is no day.
|
|
734
749
|
*/
|
|
735
|
-
export function serializeDate(day, edge, serialize = 'seconds') {
|
|
736
|
-
const text = toDateInput(day);
|
|
750
|
+
export function serializeDate(day, edge, serialize = 'seconds', timeZone = null) {
|
|
751
|
+
const text = toDateInput(day, timeZone);
|
|
737
752
|
if (!text) return null;
|
|
738
753
|
const [year, month, date] = text.split('-').map(Number);
|
|
739
|
-
const instant = edge
|
|
740
|
-
? new Date(year, month - 1, date, 23, 59, 59, 999)
|
|
741
|
-
: new Date(year, month - 1, date, 0, 0, 0, 0);
|
|
754
|
+
const instant = new Date(dayEdge(year, month - 1, date, edge, timeZone));
|
|
742
755
|
if (typeof serialize === 'function') return serialize(instant, edge);
|
|
743
756
|
if (serialize === 'ms') return instant.getTime();
|
|
744
757
|
if (serialize === 'iso') return instant.toISOString();
|
|
@@ -775,9 +788,10 @@ export function normalizeOptions(options) {
|
|
|
775
788
|
* Running it on a draft returns the same draft, so it is safe on either shape.
|
|
776
789
|
* @param {Record<string, unknown>|null|undefined} value Incoming values keyed by field.
|
|
777
790
|
* @param {Record<string, FilterPanelField>} fields Field metadata.
|
|
791
|
+
* @param {string|null} [timeZone=null] IANA zone incoming instants are read in.
|
|
778
792
|
* @returns {Record<string, unknown>}
|
|
779
793
|
*/
|
|
780
|
-
export function toDraft(value, fields) {
|
|
794
|
+
export function toDraft(value, fields, timeZone = null) {
|
|
781
795
|
const source = isPlainObject(value) ? value : {};
|
|
782
796
|
/** @type {Record<string, unknown>} */
|
|
783
797
|
const draft = {};
|
|
@@ -792,8 +806,8 @@ export function toDraft(value, fields) {
|
|
|
792
806
|
draft[key] = selectionOf(raw, normalizeOptions(field.options));
|
|
793
807
|
} else if (spec.kind === 'date') {
|
|
794
808
|
draft[key] = spec.range
|
|
795
|
-
? { from: toDateInput(readEdge(raw, 'from')), to: toDateInput(readEdge(raw, 'to')) }
|
|
796
|
-
: toDateInput(raw);
|
|
809
|
+
? { from: toDateInput(readEdge(raw, 'from'), timeZone), to: toDateInput(readEdge(raw, 'to'), timeZone) }
|
|
810
|
+
: toDateInput(raw, timeZone);
|
|
797
811
|
} else {
|
|
798
812
|
draft[key] = spec.range
|
|
799
813
|
? { from: toNumberValue(readEdge(raw, 'from'), spec), to: toNumberValue(readEdge(raw, 'to'), spec) }
|
|
@@ -809,10 +823,11 @@ export function toDraft(value, fields) {
|
|
|
809
823
|
* @param {Record<string, unknown>|null|undefined} value Values keyed by field, raw or drafted.
|
|
810
824
|
* @param {Record<string, FilterPanelField>} fields Field metadata.
|
|
811
825
|
* @param {FilterPanelSerialize} [serialize='seconds'] Date output format.
|
|
826
|
+
* @param {string|null} [timeZone=null] IANA zone calendar days are counted in.
|
|
812
827
|
* @returns {Record<string, unknown>}
|
|
813
828
|
*/
|
|
814
|
-
export function serializeFilterValue(value, fields, serialize = 'seconds') {
|
|
815
|
-
const draft = toDraft(value, fields);
|
|
829
|
+
export function serializeFilterValue(value, fields, serialize = 'seconds', timeZone = null) {
|
|
830
|
+
const draft = toDraft(value, fields, timeZone);
|
|
816
831
|
/** @type {Record<string, unknown>} */
|
|
817
832
|
const result = {};
|
|
818
833
|
for (const [key, declared] of Object.entries(fields ?? {})) {
|
|
@@ -832,14 +847,14 @@ export function serializeFilterValue(value, fields, serialize = 'seconds') {
|
|
|
832
847
|
if (spec.kind === 'date') {
|
|
833
848
|
if (!spec.range) {
|
|
834
849
|
// A single date names a day, so it serializes as that day's first instant.
|
|
835
|
-
if (raw) result[key] = serializeDate(raw, 'from', serialize);
|
|
850
|
+
if (raw) result[key] = serializeDate(raw, 'from', serialize, timeZone);
|
|
836
851
|
continue;
|
|
837
852
|
}
|
|
838
853
|
const days = /** @type {{from: string, to: string}} */ (raw);
|
|
839
854
|
/** @type {Record<string, unknown>} */
|
|
840
855
|
const range = {};
|
|
841
|
-
if (days.from) range.from = serializeDate(days.from, 'from', serialize);
|
|
842
|
-
if (days.to) range.to = serializeDate(days.to, 'to', serialize);
|
|
856
|
+
if (days.from) range.from = serializeDate(days.from, 'from', serialize, timeZone);
|
|
857
|
+
if (days.to) range.to = serializeDate(days.to, 'to', serialize, timeZone);
|
|
843
858
|
if (Object.keys(range).length > 0) result[key] = range;
|
|
844
859
|
continue;
|
|
845
860
|
}
|
|
@@ -917,11 +932,113 @@ function toNumberValue(raw, spec) {
|
|
|
917
932
|
return spec.integer ? Math.trunc(parsed) : parsed;
|
|
918
933
|
}
|
|
919
934
|
|
|
920
|
-
/**
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
935
|
+
/**
|
|
936
|
+
* Validates the `timeZone` option.
|
|
937
|
+
* @param {unknown} timeZone IANA zone name; nullish or empty means the runtime's own zone.
|
|
938
|
+
* @returns {string|null} Canonical zone name, or null for the runtime's own zone.
|
|
939
|
+
* @throws {RangeError} When the runtime does not know the zone.
|
|
940
|
+
*/
|
|
941
|
+
export function normalizePanelTimeZone(timeZone) {
|
|
942
|
+
if (timeZone === null || timeZone === undefined || timeZone === '') return null;
|
|
943
|
+
try {
|
|
944
|
+
return new Intl.DateTimeFormat('en-US', { timeZone: String(timeZone) }).resolvedOptions().timeZone;
|
|
945
|
+
} catch {
|
|
946
|
+
throw new RangeError(`Unknown time zone "${String(timeZone)}"`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* The calendar day an instant falls on, as `YYYY-MM-DD`, in a zone or the runtime's own.
|
|
952
|
+
* @param {number} milliseconds Epoch milliseconds.
|
|
953
|
+
* @param {string|null} timeZone IANA zone, or null.
|
|
954
|
+
* @returns {string}
|
|
955
|
+
*/
|
|
956
|
+
function zoneDay(milliseconds, timeZone) {
|
|
957
|
+
if (!timeZone) {
|
|
958
|
+
const date = new Date(milliseconds);
|
|
959
|
+
return isoDay(date.getFullYear(), date.getMonth(), date.getDate());
|
|
960
|
+
}
|
|
961
|
+
const wall = wallClock(milliseconds, timeZone);
|
|
962
|
+
return isoDay(wall.year, wall.month, wall.day);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Epoch milliseconds of the first (`from`) or last (`to`) instant of a calendar day in a zone.
|
|
967
|
+
* The last instant is one millisecond before the next day starts, which keeps a day that a
|
|
968
|
+
* daylight-saving change shortens or lengthens whole.
|
|
969
|
+
* @param {number} year Full year.
|
|
970
|
+
* @param {number} month Zero-based month.
|
|
971
|
+
* @param {number} day Day of the month.
|
|
972
|
+
* @param {FilterPanelEdge} edge Edge of the day.
|
|
973
|
+
* @param {string|null} timeZone IANA zone, or null for the runtime's own.
|
|
974
|
+
* @returns {number}
|
|
975
|
+
*/
|
|
976
|
+
function dayEdge(year, month, day, edge, timeZone) {
|
|
977
|
+
if (!timeZone) {
|
|
978
|
+
return (edge === 'to'
|
|
979
|
+
? new Date(year, month, day, 23, 59, 59, 999)
|
|
980
|
+
: new Date(year, month, day, 0, 0, 0, 0)).getTime();
|
|
981
|
+
}
|
|
982
|
+
if (edge !== 'to') return zoneMidnight(year, month, day, timeZone);
|
|
983
|
+
const next = new Date(Date.UTC(year, month, day + 1));
|
|
984
|
+
return zoneMidnight(next.getUTCFullYear(), next.getUTCMonth(), next.getUTCDate(), timeZone) - 1;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* The first instant of a calendar day in a zone: its midnight, or the first minute after a
|
|
989
|
+
* daylight-saving gap that swallows midnight. Two offset corrections settle it on either side of
|
|
990
|
+
* a change. This is the rule `zeyos/query.js` `calendarZone().midnightOf()` follows, so a day the
|
|
991
|
+
* panel serializes is the day a ZeyOS form stores.
|
|
992
|
+
* @param {number} year Full year.
|
|
993
|
+
* @param {number} month Zero-based month.
|
|
994
|
+
* @param {number} day Day of the month.
|
|
995
|
+
* @param {string} timeZone IANA zone.
|
|
996
|
+
* @returns {number} Epoch milliseconds.
|
|
997
|
+
*/
|
|
998
|
+
function zoneMidnight(year, month, day, timeZone) {
|
|
999
|
+
const target = Date.UTC(year, month, day);
|
|
1000
|
+
const offsetAt = (milliseconds) => {
|
|
1001
|
+
const wall = wallClock(milliseconds, timeZone);
|
|
1002
|
+
return Date.UTC(wall.year, wall.month, wall.day) + wall.seconds * 1000
|
|
1003
|
+
- Math.floor(milliseconds / 1000) * 1000;
|
|
1004
|
+
};
|
|
1005
|
+
const first = target - offsetAt(target);
|
|
1006
|
+
const second = target - offsetAt(first);
|
|
1007
|
+
let best = null;
|
|
1008
|
+
for (const candidate of [second, first]) {
|
|
1009
|
+
const wall = wallClock(candidate, timeZone);
|
|
1010
|
+
if (wall.year !== year || wall.month !== month || wall.day !== day) continue;
|
|
1011
|
+
if (!best || wall.seconds < best.seconds) best = { candidate, seconds: wall.seconds };
|
|
1012
|
+
}
|
|
1013
|
+
return best ? best.candidate : second;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* The wall clock of an instant in a zone.
|
|
1018
|
+
* @param {number} milliseconds Epoch milliseconds.
|
|
1019
|
+
* @param {string} timeZone IANA zone.
|
|
1020
|
+
* @returns {{year: number, month: number, day: number, seconds: number}} Zero-based month and
|
|
1021
|
+
* seconds since the zone's midnight.
|
|
1022
|
+
*/
|
|
1023
|
+
function wallClock(milliseconds, timeZone) {
|
|
1024
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
1025
|
+
timeZone, hourCycle: 'h23', year: 'numeric', month: 'numeric', day: 'numeric',
|
|
1026
|
+
hour: 'numeric', minute: 'numeric', second: 'numeric'
|
|
1027
|
+
});
|
|
1028
|
+
/** @type {Record<string, number>} */
|
|
1029
|
+
const parts = {};
|
|
1030
|
+
for (const part of formatter.formatToParts(new Date(milliseconds))) parts[part.type] = Number(part.value);
|
|
1031
|
+
return {
|
|
1032
|
+
year: parts.year,
|
|
1033
|
+
month: parts.month - 1,
|
|
1034
|
+
day: parts.day,
|
|
1035
|
+
seconds: (parts.hour % 24) * 3600 + parts.minute * 60 + parts.second
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/** @param {number} year @param {number} month Zero-based. @param {number} day @returns {string} `YYYY-MM-DD`. */
|
|
1040
|
+
function isoDay(year, month, day) {
|
|
1041
|
+
return `${String(year).padStart(4, '0')}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
925
1042
|
}
|
|
926
1043
|
|
|
927
1044
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
flex: none;
|
|
18
18
|
min-inline-size: 0;
|
|
19
19
|
flex-direction: column;
|
|
20
|
-
border-inline-end: 1px solid var(--zx-color-border);
|
|
20
|
+
border-inline-end: 1px solid var(--zx-color-border-subtle);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.zx-finder__list {
|
|
@@ -32,9 +32,12 @@
|
|
|
32
32
|
display: flex;
|
|
33
33
|
align-items: center;
|
|
34
34
|
gap: var(--zx-space-2);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/* A browsing row: a space-1 margin round one line of text, and the smallest radius, so a
|
|
36
|
+
column of them reads as a list rather than a stack of buttons. */
|
|
37
|
+
min-block-size: calc(var(--zx-control-height) - var(--zx-space-1));
|
|
38
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
39
|
+
border-radius: var(--zx-radius-sm);
|
|
40
|
+
line-height: 1.25;
|
|
38
41
|
cursor: pointer;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -42,30 +45,29 @@
|
|
|
42
45
|
background: var(--zx-color-bg-hover);
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/* The selected row of each column is the path you are on, not a pick: the neutral current fill,
|
|
49
|
+
the full text colour and a heavier label. */
|
|
45
50
|
.zx-finder__row[aria-selected="true"] {
|
|
46
|
-
background: var(--zx-color-bg-
|
|
47
|
-
color: var(--zx-color-
|
|
48
|
-
font-weight:
|
|
51
|
+
background: var(--zx-color-bg-current);
|
|
52
|
+
color: var(--zx-color-text);
|
|
53
|
+
font-weight: var(--zx-weight-semibold);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
.zx-finder__row:focus-visible {
|
|
52
|
-
outline:
|
|
57
|
+
outline-color: transparent;
|
|
53
58
|
box-shadow: var(--zx-focus-ring);
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/* Icons are quieter than labels, folders included; the current row's icon takes the text colour. */
|
|
56
62
|
.zx-finder__icon {
|
|
57
63
|
display: inline-grid;
|
|
58
64
|
place-items: center;
|
|
59
65
|
flex: none;
|
|
60
|
-
color: var(--zx-color-text-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.zx-finder__row[data-branch] .zx-finder__icon {
|
|
64
|
-
color: var(--zx-color-warning);
|
|
66
|
+
color: var(--zx-color-text-subtle);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
.zx-finder__row[aria-selected="true"] .zx-finder__icon {
|
|
68
|
-
color:
|
|
70
|
+
color: var(--zx-color-text);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
.zx-finder__label {
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.zx-form__actions .zx-btn[type="submit"] {
|
|
32
|
-
border-color: var(--zx-color-accent);
|
|
33
|
-
background: var(--zx-color-accent);
|
|
34
|
-
color: var(--zx-color-on-accent);
|
|
32
|
+
border-color: var(--zx-color-accent-solid);
|
|
33
|
+
background: var(--zx-color-accent-solid);
|
|
34
|
+
color: var(--zx-color-on-accent-solid);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.zx-form__actions .zx-btn[type="submit"]:hover {
|
|
38
|
-
|
|
38
|
+
border-color: var(--zx-color-accent-solid-hover);
|
|
39
|
+
background: var(--zx-color-accent-solid-hover);
|
|
39
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
3
|
import { icon as createIcon } from '../../core/icons.js';
|
|
4
|
+
import { uid } from '../../core/util.js';
|
|
4
5
|
import { Fieldset } from '../fieldset/fieldset.js';
|
|
5
6
|
import { coerceFloat, coerceInteger } from '../field/field.js';
|
|
6
7
|
|
|
@@ -22,6 +23,9 @@ import { coerceFloat, coerceInteger } from '../field/field.js';
|
|
|
22
23
|
* @property {Array<Fieldset|import('../fieldset/fieldset.js').FieldsetOptions>} [fieldsets=[]] Initial fieldsets.
|
|
23
24
|
* @property {FormAction[]} [actions=[]] Form action descriptors.
|
|
24
25
|
* @property {boolean} [novalidate=true] Native `novalidate` state.
|
|
26
|
+
* @property {string|null} [idPrefix=null] Prefix of the DOM ids generated for the fields of the
|
|
27
|
+
* fieldsets this form builds, as `<idPrefix>-<key>`. Null generates one per form instance, so a
|
|
28
|
+
* form mounted twice never repeats an id. A fieldset's own `idPrefix` wins.
|
|
25
29
|
* @property {(event: CustomEvent<{values: Record<string, unknown>}>) => void} [onsubmit] Submit listener.
|
|
26
30
|
* @property {(event: CustomEvent<{errors: Record<string, string>}>) => void} [oninvalid] Invalid listener.
|
|
27
31
|
* @property {(event: CustomEvent<{id: string, value: unknown}>) => void} [onchange] Field change listener.
|
|
@@ -42,6 +46,7 @@ export class Form extends Component {
|
|
|
42
46
|
fieldsets: [],
|
|
43
47
|
actions: [],
|
|
44
48
|
novalidate: true,
|
|
49
|
+
idPrefix: null,
|
|
45
50
|
msg: {
|
|
46
51
|
'form.required': 'This field is required.',
|
|
47
52
|
'form.int': 'Enter a whole number.',
|
|
@@ -56,6 +61,7 @@ export class Form extends Component {
|
|
|
56
61
|
this._ownedFieldsets = new Set();
|
|
57
62
|
this._origins = new Map();
|
|
58
63
|
this._ownedNodes = [];
|
|
64
|
+
this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid('zx-form');
|
|
59
65
|
this._novalidateAttribute = root.getAttribute('novalidate');
|
|
60
66
|
root.toggleAttribute('novalidate', Boolean(this.options.novalidate));
|
|
61
67
|
|
|
@@ -81,13 +87,16 @@ export class Form extends Component {
|
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
/**
|
|
84
|
-
* Adds an existing fieldset or constructs one from options.
|
|
90
|
+
* Adds an existing fieldset or constructs one from options. A constructed fieldset generates its
|
|
91
|
+
* field ids from the form's `idPrefix` unless its options carry their own.
|
|
85
92
|
* @param {Fieldset|import('../fieldset/fieldset.js').FieldsetOptions} fieldset Fieldset source.
|
|
86
93
|
* @returns {Fieldset}
|
|
87
94
|
*/
|
|
88
95
|
addFieldset(fieldset) {
|
|
89
96
|
const owned = !(fieldset instanceof Fieldset);
|
|
90
|
-
const instance = owned
|
|
97
|
+
const instance = owned
|
|
98
|
+
? new Fieldset(null, { ...(fieldset ?? {}), idPrefix: fieldset?.idPrefix ?? this._idPrefix })
|
|
99
|
+
: fieldset;
|
|
91
100
|
if (this._fieldsets.includes(instance)) return instance;
|
|
92
101
|
if (owned) this._ownedFieldsets.add(instance);
|
|
93
102
|
else this._origins.set(instance, { parent: instance.el.parentNode, next: instance.el.nextSibling });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { h, safeHref } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
|
-
import { avatarInitials } from '../avatar/avatar.js';
|
|
4
|
+
import { AVATAR_TONES, avatarInitials, avatarTone } from '../avatar/avatar.js';
|
|
5
5
|
import { badge } from '../badge/badge.js';
|
|
6
6
|
|
|
7
7
|
/** @typedef {Record<string, any>} KanbanRecord */
|
|
@@ -28,6 +28,9 @@ import { badge } from '../badge/badge.js';
|
|
|
28
28
|
* @property {string} name Person's name, used as the accessible label.
|
|
29
29
|
* @property {string|null} src Safe image URL, or null for the initials fallback.
|
|
30
30
|
* @property {string} initials Fallback initials.
|
|
31
|
+
* @property {import('../avatar/avatar.js').AvatarTone} tone Fallback tint: a descriptor's own
|
|
32
|
+
* `tone` when it names one of `AVATAR_TONES`, otherwise `avatarTone(name)` — the colour the same
|
|
33
|
+
* person has in every Avatar.
|
|
31
34
|
*/
|
|
32
35
|
|
|
33
36
|
/** Semantic intents an indicator may carry, in the order they are documented. */
|
|
@@ -132,6 +135,8 @@ export function createKanbanProgress(progress, label, valueText) {
|
|
|
132
135
|
/**
|
|
133
136
|
* Normalizes one or more responsible people. Names, name lists, and descriptor objects all
|
|
134
137
|
* resolve to the same shape, and an unsafe image URL degrades to initials rather than failing.
|
|
138
|
+
* A descriptor may name its `tone`; otherwise the tone is derived from the name exactly as Avatar
|
|
139
|
+
* derives it.
|
|
135
140
|
* @param {unknown} value Raw field value.
|
|
136
141
|
* @returns {KanbanAssignee[]} Normalized people, in the order supplied.
|
|
137
142
|
*/
|
|
@@ -148,7 +153,8 @@ export function resolveKanbanAssignees(value) {
|
|
|
148
153
|
if (!name) return null;
|
|
149
154
|
const src = safeHref(source.src ?? source.image ?? null);
|
|
150
155
|
const initials = source.initials == null ? avatarInitials(name) : String(source.initials);
|
|
151
|
-
|
|
156
|
+
const tone = AVATAR_TONES.includes(source.tone) ? source.tone : avatarTone(name);
|
|
157
|
+
return { name, src, initials, tone };
|
|
152
158
|
}).filter(Boolean));
|
|
153
159
|
}
|
|
154
160
|
|
|
@@ -185,8 +191,10 @@ export function createKanbanAvatars(people, max, groupLabel, overflowLabel) {
|
|
|
185
191
|
const split = kanbanAvatarLimit(max, people.length);
|
|
186
192
|
const shown = people.slice(0, split.shown);
|
|
187
193
|
const hidden = split.hidden;
|
|
194
|
+
// Each face carries its tone, so the initials sit on the tint the person has in every Avatar.
|
|
188
195
|
const faces = shown.map((person) => h('span', {
|
|
189
|
-
class: 'zx-kanban-view__avatar', ariaHidden: 'true', title: person.name || undefined
|
|
196
|
+
class: 'zx-kanban-view__avatar', ariaHidden: 'true', title: person.name || undefined,
|
|
197
|
+
dataset: { tone: person.tone ?? avatarTone(person.name) }
|
|
190
198
|
}, person.src
|
|
191
199
|
? h('img', { class: 'zx-kanban-view__avatar-image', src: person.src, alt: '', draggable: false })
|
|
192
200
|
: h('span', { class: 'zx-kanban-view__avatar-initials' }, person.initials)));
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
.zx-kanban-view__move-handle[aria-pressed="true"] {
|
|
123
|
-
background: var(--zx-color-
|
|
124
|
-
color: var(--zx-color-
|
|
123
|
+
background: var(--zx-color-bg-current);
|
|
124
|
+
color: var(--zx-color-text);
|
|
125
125
|
cursor: grabbing;
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
.zx-kanban-view__collapse:focus-visible,
|
|
135
135
|
.zx-kanban-view__move-handle:focus-visible,
|
|
136
136
|
.zx-kanban-view .zx-record-card:focus-visible {
|
|
137
|
-
outline:
|
|
137
|
+
outline-color: transparent;
|
|
138
138
|
box-shadow: var(--zx-focus-ring);
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
.zx-kanban-view :where(.zx-record-card[data-variant="outlined"]) {
|
|
191
|
-
box-shadow: var(--zx-shadow
|
|
191
|
+
box-shadow: var(--zx-surface-shadow);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/* The shared card anatomy stays intact; Kanban constrains media to a compact thumbnail. */
|
|
@@ -348,7 +348,7 @@
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
.zx-kanban-view .zx-record-card__eyebrow-group[data-card-slot="eyebrow-start"] .zx-record-card__field {
|
|
351
|
-
border-radius: var(--zx-radius-
|
|
351
|
+
border-radius: var(--zx-radius-sm);
|
|
352
352
|
background: var(--zx-color-bg-muted);
|
|
353
353
|
padding: calc(var(--zx-space-1) / 2) var(--zx-space-2);
|
|
354
354
|
}
|
|
@@ -419,6 +419,12 @@
|
|
|
419
419
|
.zx-kanban-view .zx-record-card[data-grabbed="true"] {
|
|
420
420
|
outline: 2px solid var(--zx-color-accent);
|
|
421
421
|
outline-offset: 2px;
|
|
422
|
+
/* A card picked up from the keyboard is the item being dragged, so it lifts like one. */
|
|
423
|
+
box-shadow: var(--zx-drag-shadow);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.zx-kanban-view .zx-record-card[data-grabbed="true"]:focus-visible {
|
|
427
|
+
box-shadow: var(--zx-focus-ring), var(--zx-drag-shadow);
|
|
422
428
|
}
|
|
423
429
|
|
|
424
430
|
.zx-kanban-view__empty {
|
|
@@ -470,10 +476,6 @@
|
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
|
|
473
|
-
[data-zx-density="compact"] .zx-kanban-view {
|
|
474
|
-
--zx-kanban-card-padding-inline: var(--zx-space-2);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
479
|
@media (prefers-reduced-motion: no-preference) {
|
|
478
480
|
.zx-kanban-view__collapse,
|
|
479
481
|
.zx-kanban-view__move-handle,
|
|
@@ -561,7 +563,7 @@
|
|
|
561
563
|
}
|
|
562
564
|
|
|
563
565
|
.zx-kanban-view__add:focus-visible {
|
|
564
|
-
outline:
|
|
566
|
+
outline-color: transparent;
|
|
565
567
|
box-shadow: var(--zx-focus-ring);
|
|
566
568
|
}
|
|
567
569
|
|
|
@@ -651,7 +653,8 @@
|
|
|
651
653
|
border: 1px solid var(--zx-color-border);
|
|
652
654
|
border-radius: var(--zx-radius-md);
|
|
653
655
|
background: var(--zx-color-bg-raised);
|
|
654
|
-
|
|
656
|
+
/* The item in flight: lifted above every resting card by the shared drag shadow. */
|
|
657
|
+
box-shadow: var(--zx-drag-shadow);
|
|
655
658
|
padding: var(--zx-space-1) var(--zx-space-2);
|
|
656
659
|
color: var(--zx-color-text);
|
|
657
660
|
font-size: var(--zx-text-xs);
|
|
@@ -704,7 +707,6 @@
|
|
|
704
707
|
color: var(--zx-color-text-muted);
|
|
705
708
|
font-size: var(--zx-text-xs);
|
|
706
709
|
font-variant-numeric: tabular-nums;
|
|
707
|
-
letter-spacing: .02em;
|
|
708
710
|
text-overflow: ellipsis;
|
|
709
711
|
white-space: nowrap;
|
|
710
712
|
}
|
|
@@ -767,6 +769,11 @@
|
|
|
767
769
|
min-inline-size: 0;
|
|
768
770
|
}
|
|
769
771
|
|
|
772
|
+
/*
|
|
773
|
+
* A face is a small Avatar: the initials on the tint of the tone the component derives from the
|
|
774
|
+
* name with Avatar's own hash (`data-tone`), so a person has the same colour on the board as
|
|
775
|
+
* everywhere else. The surface-coloured edge is what separates overlapping faces.
|
|
776
|
+
*/
|
|
770
777
|
.zx-kanban-view__avatar {
|
|
771
778
|
display: inline-grid;
|
|
772
779
|
flex: none;
|
|
@@ -776,17 +783,27 @@
|
|
|
776
783
|
overflow: hidden;
|
|
777
784
|
border: 1px solid var(--zx-color-bg-surface);
|
|
778
785
|
border-radius: var(--zx-radius-full);
|
|
779
|
-
background: var(--zx-color-bg-muted);
|
|
780
|
-
color: var(--zx-color-text-muted);
|
|
786
|
+
background: var(--zx-kanban-avatar-bg, var(--zx-color-bg-muted));
|
|
787
|
+
color: var(--zx-kanban-avatar-fg, var(--zx-color-text-muted));
|
|
781
788
|
}
|
|
782
789
|
|
|
790
|
+
.zx-kanban-view__avatar[data-tone="blue"] { --zx-kanban-avatar-bg: var(--zx-color-tone-blue-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-blue-fg); }
|
|
791
|
+
.zx-kanban-view__avatar[data-tone="teal"] { --zx-kanban-avatar-bg: var(--zx-color-tone-teal-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-teal-fg); }
|
|
792
|
+
.zx-kanban-view__avatar[data-tone="green"] { --zx-kanban-avatar-bg: var(--zx-color-tone-green-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-green-fg); }
|
|
793
|
+
.zx-kanban-view__avatar[data-tone="amber"] { --zx-kanban-avatar-bg: var(--zx-color-tone-amber-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-amber-fg); }
|
|
794
|
+
.zx-kanban-view__avatar[data-tone="orange"] { --zx-kanban-avatar-bg: var(--zx-color-tone-orange-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-orange-fg); }
|
|
795
|
+
.zx-kanban-view__avatar[data-tone="red"] { --zx-kanban-avatar-bg: var(--zx-color-tone-red-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-red-fg); }
|
|
796
|
+
.zx-kanban-view__avatar[data-tone="pink"] { --zx-kanban-avatar-bg: var(--zx-color-tone-pink-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-pink-fg); }
|
|
797
|
+
.zx-kanban-view__avatar[data-tone="violet"] { --zx-kanban-avatar-bg: var(--zx-color-tone-violet-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-violet-fg); }
|
|
798
|
+
.zx-kanban-view__avatar[data-tone="slate"] { --zx-kanban-avatar-bg: var(--zx-color-tone-slate-bg); --zx-kanban-avatar-fg: var(--zx-color-tone-slate-fg); }
|
|
799
|
+
|
|
783
800
|
.zx-kanban-view__avatar + .zx-kanban-view__avatar {
|
|
784
801
|
margin-inline-start: calc(var(--zx-space-2) * -1);
|
|
785
802
|
}
|
|
786
803
|
|
|
787
804
|
.zx-kanban-view__avatar[data-overflow="true"] {
|
|
788
|
-
background: var(--zx-color-
|
|
789
|
-
color: var(--zx-color-
|
|
805
|
+
background: var(--zx-color-bg-current);
|
|
806
|
+
color: var(--zx-color-text-muted);
|
|
790
807
|
}
|
|
791
808
|
|
|
792
809
|
.zx-kanban-view__avatar-image {
|
|
@@ -795,13 +812,20 @@
|
|
|
795
812
|
object-fit: cover;
|
|
796
813
|
}
|
|
797
814
|
|
|
815
|
+
/* Avatar's initials at the small size: 0.4 of the face, never under 10px, semibold. */
|
|
798
816
|
.zx-kanban-view__avatar-initials {
|
|
799
|
-
font-size:
|
|
800
|
-
font-weight:
|
|
817
|
+
font-size: max(10px, calc(1.5rem * .4));
|
|
818
|
+
font-weight: var(--zx-weight-semibold);
|
|
819
|
+
letter-spacing: 0;
|
|
801
820
|
line-height: 1;
|
|
802
821
|
text-transform: uppercase;
|
|
803
822
|
}
|
|
804
823
|
|
|
824
|
+
/* The overflow chip is a count, not a person: it keeps the text size it can be read at. */
|
|
825
|
+
.zx-kanban-view__avatar[data-overflow="true"] .zx-kanban-view__avatar-initials {
|
|
826
|
+
font-size: var(--zx-text-xs);
|
|
827
|
+
}
|
|
828
|
+
|
|
805
829
|
/* ── Move handle ──────────────────────────────────────────────────────────────────────────── */
|
|
806
830
|
|
|
807
831
|
/* The card is the pointer drag source, so the handle is keyboard furniture: announced and in tab
|