@zeyos/zx 4.2.0 → 4.3.4
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 +148 -1
- package/README.md +5 -3
- package/dist/types/components/calendar/calendar-model.d.ts +22 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -2
- package/dist/types/components/card-view/card-view.d.ts +1 -1
- package/dist/types/components/card-view/record-card.d.ts +47 -5
- package/dist/types/components/dialog/dialog.d.ts +6 -1
- package/dist/types/components/dropdown/dropdown.d.ts +5 -0
- package/dist/types/components/kanban-view/kanban-policy.d.ts +268 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +669 -24
- package/dist/types/components/launcher/launcher.d.ts +5 -0
- package/dist/types/components/message/message.d.ts +9 -1
- package/dist/types/components/modal/modal.d.ts +44 -7
- package/dist/types/components/sheet/sheet.d.ts +22 -12
- package/dist/types/core/component.d.ts +6 -1
- package/dist/types/core/overlay-host.d.ts +12 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/zx-zeyos.esm.js +173 -43
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +876 -230
- package/dist/zx.esm.js +2053 -338
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +2053 -338
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +250 -2
- package/docs/llms.md +67 -48
- package/docs/llms.txt +1 -3
- package/package.json +1 -2
- package/src/components/account-menu/account-menu.css +1 -1
- package/src/components/app-rail/app-rail.css +1 -1
- package/src/components/calendar/calendar-model.js +45 -0
- package/src/components/calendar/calendar.css +96 -34
- package/src/components/calendar/calendar.js +122 -29
- package/src/components/card-view/card-view.css +211 -9
- package/src/components/card-view/card-view.js +7 -7
- package/src/components/card-view/record-card.js +92 -19
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/context-menu/context-menu.js +5 -1
- package/src/components/date-picker/date-picker.css +18 -11
- package/src/components/datebox/date-range-box.css +4 -2
- package/src/components/datebox/datebox.css +4 -2
- package/src/components/dialog/dialog.css +4 -3
- package/src/components/dialog/dialog.js +2 -1
- package/src/components/dropdown/dropdown.css +4 -2
- package/src/components/dropdown/dropdown.js +7 -2
- package/src/components/kanban-view/kanban-policy.js +412 -0
- package/src/components/kanban-view/kanban-view.css +480 -36
- package/src/components/kanban-view/kanban-view.js +1514 -235
- package/src/components/launcher/launcher.css +10 -8
- package/src/components/launcher/launcher.js +9 -2
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +15 -25
- package/src/components/message/message.js +17 -11
- package/src/components/modal/modal.css +6 -4
- package/src/components/modal/modal.js +99 -25
- package/src/components/select/select.css +9 -7
- package/src/components/sheet/sheet.css +25 -7
- package/src/components/sheet/sheet.js +43 -23
- package/src/components/table/table.css +5 -3
- package/src/components/tag-picker/tag-picker.css +5 -3
- package/src/components/tooltip/tooltip.css +4 -2
- package/src/components/tooltip/tooltip.js +4 -1
- package/src/core/component.js +8 -3
- package/src/core/overlay-host.js +23 -0
- package/src/index.js +8 -3
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +83 -8
- package/DESIGN-SYSTEM.md +0 -182
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
.zx-card-view {
|
|
2
|
-
--zx-card-view-min-card-width:
|
|
2
|
+
--zx-card-view-min-card-width: 15rem;
|
|
3
3
|
--zx-card-view-column-basis: 0%;
|
|
4
|
+
--zx-card-view-card-padding: var(--zx-space-3);
|
|
5
|
+
--zx-card-view-card-gap: var(--zx-space-2);
|
|
6
|
+
--zx-card-view-grid-gap: var(--zx-space-3);
|
|
7
|
+
--zx-card-view-preview-size: 1.75rem;
|
|
4
8
|
inline-size: 100%;
|
|
5
9
|
min-inline-size: 0;
|
|
6
10
|
container-type: inline-size;
|
|
@@ -44,22 +48,23 @@
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
.zx-card-view__group + .zx-card-view__group {
|
|
47
|
-
margin-block-start: var(--zx-space-
|
|
51
|
+
margin-block-start: var(--zx-space-4);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
.zx-card-view__group-heading {
|
|
51
|
-
margin: 0 0 var(--zx-space-
|
|
55
|
+
margin: 0 0 var(--zx-space-2);
|
|
52
56
|
color: var(--zx-color-text);
|
|
53
|
-
font-size: var(--zx-text-
|
|
54
|
-
|
|
57
|
+
font-size: var(--zx-text-sm);
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
line-height: 1.3;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
.zx-card-view__list {
|
|
58
63
|
display: grid;
|
|
59
|
-
grid-template-columns: repeat(auto-
|
|
64
|
+
grid-template-columns: repeat(auto-fill,
|
|
60
65
|
minmax(min(100%, max(var(--zx-card-view-min-card-width),
|
|
61
|
-
calc(var(--zx-card-view-column-basis) - var(--zx-
|
|
62
|
-
gap: var(--zx-
|
|
66
|
+
calc(var(--zx-card-view-column-basis) - var(--zx-card-view-grid-gap)))), 1fr));
|
|
67
|
+
gap: var(--zx-card-view-grid-gap);
|
|
63
68
|
min-inline-size: 0;
|
|
64
69
|
margin: 0;
|
|
65
70
|
padding: 0;
|
|
@@ -102,7 +107,7 @@
|
|
|
102
107
|
box-shadow: var(--zx-focus-ring);
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
.zx-record-card[
|
|
110
|
+
.zx-record-card[data-selected="true"] {
|
|
106
111
|
border-color: var(--zx-color-accent);
|
|
107
112
|
box-shadow: inset 0 0 0 1px var(--zx-color-accent);
|
|
108
113
|
}
|
|
@@ -181,6 +186,40 @@
|
|
|
181
186
|
min-inline-size: 0;
|
|
182
187
|
}
|
|
183
188
|
|
|
189
|
+
.zx-record-card__eyebrow {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: flex-start;
|
|
192
|
+
gap: var(--zx-space-2);
|
|
193
|
+
min-inline-size: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.zx-record-card__eyebrow-group,
|
|
197
|
+
.zx-record-card__title-prefix {
|
|
198
|
+
display: flex;
|
|
199
|
+
flex-wrap: wrap;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: var(--zx-space-1);
|
|
202
|
+
min-inline-size: 0;
|
|
203
|
+
margin: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.zx-record-card__eyebrow-group[data-card-slot="eyebrow-end"] {
|
|
207
|
+
justify-content: flex-end;
|
|
208
|
+
margin-inline-start: auto;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.zx-record-card__title-row {
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: flex-start;
|
|
214
|
+
gap: var(--zx-space-2);
|
|
215
|
+
min-inline-size: 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.zx-record-card__title-row > .zx-record-card__heading {
|
|
219
|
+
flex: 1 1 auto;
|
|
220
|
+
min-inline-size: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
184
223
|
.zx-record-card__heading {
|
|
185
224
|
margin: 0;
|
|
186
225
|
overflow-wrap: anywhere;
|
|
@@ -387,3 +426,166 @@
|
|
|
387
426
|
box-shadow var(--zx-dur-fast) var(--zx-ease);
|
|
388
427
|
}
|
|
389
428
|
}
|
|
429
|
+
|
|
430
|
+
/*
|
|
431
|
+
* CardView intentionally presents the shared record-card anatomy more densely than Kanban. Keep
|
|
432
|
+
* these selectors scoped: Kanban owns its own board-card rhythm while both views retain the same
|
|
433
|
+
* safe, semantic markup and delegated interaction hooks.
|
|
434
|
+
*/
|
|
435
|
+
.zx-card-view .zx-record-card {
|
|
436
|
+
border-radius: var(--zx-radius-md);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.zx-card-view .zx-record-card:has(> .zx-record-card__preview) {
|
|
440
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
441
|
+
align-items: start;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.zx-card-view .zx-record-card__preview {
|
|
445
|
+
inline-size: var(--zx-card-view-preview-size);
|
|
446
|
+
block-size: var(--zx-card-view-preview-size);
|
|
447
|
+
margin-block-start: var(--zx-card-view-card-padding);
|
|
448
|
+
margin-inline-start: var(--zx-card-view-card-padding);
|
|
449
|
+
border: 0;
|
|
450
|
+
border-radius: var(--zx-radius-sm);
|
|
451
|
+
aspect-ratio: 1;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.zx-card-view .zx-record-card__preview .zx-icon {
|
|
455
|
+
inline-size: .875rem;
|
|
456
|
+
block-size: .875rem;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.zx-card-view .zx-record-card__body {
|
|
460
|
+
gap: var(--zx-card-view-card-gap);
|
|
461
|
+
padding: var(--zx-card-view-card-padding);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.zx-card-view .zx-record-card:has(> .zx-record-card__preview) .zx-record-card__body {
|
|
465
|
+
padding-inline-start: var(--zx-card-view-card-gap);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.zx-card-view .zx-record-card__header {
|
|
469
|
+
gap: var(--zx-space-2);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.zx-card-view .zx-record-card__selection {
|
|
473
|
+
min-block-size: 1.25rem;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.zx-card-view .zx-record-card__heading {
|
|
477
|
+
font-size: var(--zx-text-md);
|
|
478
|
+
font-weight: 600;
|
|
479
|
+
line-height: 1.3;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.zx-card-view .zx-record-card__primary,
|
|
483
|
+
.zx-card-view .zx-record-card__title {
|
|
484
|
+
display: -webkit-box;
|
|
485
|
+
overflow: hidden;
|
|
486
|
+
-webkit-box-orient: vertical;
|
|
487
|
+
-webkit-line-clamp: 2;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.zx-card-view .zx-record-card__subtitle {
|
|
491
|
+
margin-block-start: calc(var(--zx-space-1) / 2);
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
font-size: var(--zx-text-xs);
|
|
494
|
+
line-height: 1.3;
|
|
495
|
+
text-overflow: ellipsis;
|
|
496
|
+
white-space: nowrap;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.zx-card-view .zx-record-card__actions {
|
|
500
|
+
flex-wrap: nowrap;
|
|
501
|
+
gap: calc(var(--zx-space-1) / 2);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.zx-card-view .zx-record-card__action {
|
|
505
|
+
min-block-size: 1.75rem;
|
|
506
|
+
border-color: transparent;
|
|
507
|
+
background: transparent;
|
|
508
|
+
padding-inline: var(--zx-space-2);
|
|
509
|
+
font-size: var(--zx-text-xs);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.zx-card-view .zx-record-card__metadata {
|
|
513
|
+
display: flex;
|
|
514
|
+
flex-wrap: wrap;
|
|
515
|
+
gap: var(--zx-space-1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.zx-card-view .zx-record-card__field {
|
|
519
|
+
display: inline-flex;
|
|
520
|
+
flex: 0 1 auto;
|
|
521
|
+
align-items: center;
|
|
522
|
+
gap: var(--zx-space-1);
|
|
523
|
+
max-inline-size: 100%;
|
|
524
|
+
border: 1px solid var(--zx-color-border);
|
|
525
|
+
border-radius: var(--zx-radius-full);
|
|
526
|
+
background: var(--zx-color-bg-muted);
|
|
527
|
+
padding: calc(var(--zx-space-1) / 2) var(--zx-space-2);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.zx-card-view .zx-record-card__label {
|
|
531
|
+
flex: none;
|
|
532
|
+
font-size: var(--zx-text-xs);
|
|
533
|
+
font-weight: 500;
|
|
534
|
+
line-height: 1.2;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.zx-card-view .zx-record-card__value {
|
|
538
|
+
overflow: hidden;
|
|
539
|
+
font-size: var(--zx-text-xs);
|
|
540
|
+
line-height: 1.2;
|
|
541
|
+
text-overflow: ellipsis;
|
|
542
|
+
white-space: nowrap;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.zx-card-view .zx-card-view__group-empty {
|
|
546
|
+
padding: var(--zx-space-3);
|
|
547
|
+
font-size: var(--zx-text-sm);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.zx-card-view .zx-card-view__empty {
|
|
551
|
+
min-block-size: 6rem;
|
|
552
|
+
border-radius: var(--zx-radius-md);
|
|
553
|
+
padding: var(--zx-space-4);
|
|
554
|
+
font-size: var(--zx-text-sm);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.zx-card-view .zx-card-view__skeleton {
|
|
558
|
+
grid-template-columns: var(--zx-card-view-preview-size) minmax(0, 1fr);
|
|
559
|
+
gap: var(--zx-card-view-card-gap);
|
|
560
|
+
padding: var(--zx-card-view-card-padding);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.zx-card-view .zx-card-view__skeleton-preview {
|
|
564
|
+
grid-row: 1 / span 2;
|
|
565
|
+
inline-size: var(--zx-card-view-preview-size);
|
|
566
|
+
margin: 0;
|
|
567
|
+
aspect-ratio: 1;
|
|
568
|
+
border-radius: var(--zx-radius-sm);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.zx-card-view .zx-card-view__skeleton-line {
|
|
572
|
+
min-inline-size: 0;
|
|
573
|
+
block-size: .625rem;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.zx-card-view .zx-card-view__skeleton-line:nth-child(3) {
|
|
577
|
+
inline-size: 62%;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.zx-card-view .zx-card-view__skeleton-line:nth-child(4) {
|
|
581
|
+
grid-column: 1 / -1;
|
|
582
|
+
inline-size: 42%;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@media (pointer: coarse) {
|
|
586
|
+
.zx-card-view .zx-record-card__selection,
|
|
587
|
+
.zx-card-view .zx-record-card__action {
|
|
588
|
+
min-inline-size: 44px;
|
|
589
|
+
min-block-size: 44px;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
* @property {string|((record:ViewRecord,index:number)=>unknown)|null} [groupBy=null]
|
|
40
40
|
* Field id or local group resolver.
|
|
41
41
|
* @property {unknown[]} [groupOrder=[]] Preferred group ids; missing ids render as empty groups.
|
|
42
|
-
* @property {number|string} [minCardWidth='
|
|
42
|
+
* @property {number|string} [minCardWidth='15rem'] Responsive minimum card width.
|
|
43
43
|
* @property {number|null} [maxColumns=null] Maximum cards per row; null is unconstrained.
|
|
44
44
|
* @property {'outlined'|'raised'|'filled'} [variant='outlined'] Card surface treatment.
|
|
45
45
|
* @property {number} [loadingCount=6] Skeleton cards shown while loading.
|
|
@@ -87,7 +87,7 @@ export class CardView extends RecordView {
|
|
|
87
87
|
actions: [],
|
|
88
88
|
groupBy: null,
|
|
89
89
|
groupOrder: [],
|
|
90
|
-
minCardWidth: '
|
|
90
|
+
minCardWidth: '15rem',
|
|
91
91
|
maxColumns: null,
|
|
92
92
|
variant: 'outlined',
|
|
93
93
|
loadingCount: 6,
|
|
@@ -329,7 +329,7 @@ export class CardView extends RecordView {
|
|
|
329
329
|
for (const [id, card] of this._cardNodesById) {
|
|
330
330
|
const selected = this._isViewSelected(id);
|
|
331
331
|
card.dataset.selected = String(selected);
|
|
332
|
-
if (this.options.selectable) card.setAttribute('aria-
|
|
332
|
+
if (this.options.selectable) card.setAttribute('aria-description', selected ? 'Selected' : 'Not selected');
|
|
333
333
|
const checkbox = /** @type {HTMLInputElement|null} */ (card.querySelector('[data-record-selection]'));
|
|
334
334
|
if (checkbox) checkbox.checked = selected;
|
|
335
335
|
}
|
|
@@ -469,11 +469,11 @@ function normalizeGroupId(value) {
|
|
|
469
469
|
|
|
470
470
|
/** @param {unknown} value @returns {string} */
|
|
471
471
|
export function normalizeCardWidth(value) {
|
|
472
|
-
if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? `${value}px` : '
|
|
472
|
+
if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? `${value}px` : '15rem';
|
|
473
473
|
const text = String(value ?? '').trim();
|
|
474
|
-
if (!text || text.length > 128 || /[;{}\u0000-\u001f\u007f]/.test(text)) return '
|
|
475
|
-
if (/(?:url|image|image-set|cross-fade|element|expression)\s*\(/i.test(text)) return '
|
|
476
|
-
if (typeof globalThis.CSS?.supports === 'function' && !globalThis.CSS.supports('width', text)) return '
|
|
474
|
+
if (!text || text.length > 128 || /[;{}\u0000-\u001f\u007f]/.test(text)) return '15rem';
|
|
475
|
+
if (/(?:url|image|image-set|cross-fade|element|expression)\s*\(/i.test(text)) return '15rem';
|
|
476
|
+
if (typeof globalThis.CSS?.supports === 'function' && !globalThis.CSS.supports('width', text)) return '15rem';
|
|
477
477
|
return text;
|
|
478
478
|
}
|
|
479
479
|
|
|
@@ -7,6 +7,15 @@ import { readViewField, renderViewField } from '../view/record-view.js';
|
|
|
7
7
|
/** @typedef {Record<string, any>} ViewRecord */
|
|
8
8
|
/** @typedef {import('../view/record-view.js').ViewField} ViewField */
|
|
9
9
|
/** @typedef {string|((record:ViewRecord,index:number)=>unknown)|null} RecordCardValueSource */
|
|
10
|
+
/** @typedef {'eyebrow-start'|'eyebrow-end'|'title-prefix'} RecordCardFieldSlot */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} RecordCardFieldGroups
|
|
14
|
+
* @property {ViewField[]} eyebrowStart Fields shown at the logical start of the eyebrow row.
|
|
15
|
+
* @property {ViewField[]} eyebrowEnd Fields shown at the logical end of the eyebrow row.
|
|
16
|
+
* @property {ViewField[]} titlePrefix Fields shown immediately before, but outside, the heading.
|
|
17
|
+
* @property {ViewField[]} metadata Remaining labelled metadata fields.
|
|
18
|
+
*/
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* @typedef {Object} RecordCardPreviewDescriptor
|
|
@@ -73,11 +82,12 @@ import { readViewField, renderViewField } from '../view/record-view.js';
|
|
|
73
82
|
* becomes text, URLs are normalized before reaching native attributes, and actions expose stable
|
|
74
83
|
* data attributes for a component-owned delegated listener.
|
|
75
84
|
*
|
|
76
|
-
* The returned `<li>` belongs in a semantic `<ul>`/`<ol>`. When selectable
|
|
77
|
-
* `aria-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
85
|
+
* The returned `<li>` belongs in a semantic `<ul>`/`<ol>`. When selectable its global
|
|
86
|
+
* `aria-description` announces selection without applying the unsupported `aria-selected` state
|
|
87
|
+
* to a listitem; multi-selection also adds a native checkbox. It deliberately remains a listitem
|
|
88
|
+
* rather than becoming an ARIA option because cards may contain native links and buttons, which
|
|
89
|
+
* the listbox pattern does not permit inside an option. Preview images are paired with a stable
|
|
90
|
+
* fallback. The owning component listens for image `error` and marks the fallback.
|
|
81
91
|
*
|
|
82
92
|
* @param {ViewRecord} record Record to render.
|
|
83
93
|
* @param {number} index Current display index.
|
|
@@ -112,6 +122,13 @@ export function createRecordCard(record, index, options = {}) {
|
|
|
112
122
|
class: 'zx-record-card__heading',
|
|
113
123
|
id: headingId
|
|
114
124
|
}, headingContent);
|
|
125
|
+
const fieldGroups = partitionRecordCardFields(visibleFields, titleSource, subtitleSource);
|
|
126
|
+
const eyebrow = createEyebrow(record, index, fieldGroups.eyebrowStart, fieldGroups.eyebrowEnd);
|
|
127
|
+
const titlePrefix = createFieldList(record, index, fieldGroups.titlePrefix,
|
|
128
|
+
'zx-record-card__title-prefix', 'title-prefix');
|
|
129
|
+
const titleRow = titlePrefix
|
|
130
|
+
? h('div', { class: 'zx-record-card__title-row' }, titlePrefix, heading)
|
|
131
|
+
: heading;
|
|
115
132
|
const selection = selectable === 'multi' ? h('label', {
|
|
116
133
|
class: 'zx-record-card__selection'
|
|
117
134
|
}, h('input', {
|
|
@@ -127,15 +144,15 @@ export function createRecordCard(record, index, options = {}) {
|
|
|
127
144
|
ariaLabel: `Actions for ${textAlternative(title) || 'record'}`
|
|
128
145
|
}, controls, actionNodes) : null;
|
|
129
146
|
const header = h('header', { class: 'zx-record-card__header' }, selection,
|
|
130
|
-
h('div', { class: 'zx-record-card__titles' },
|
|
147
|
+
h('div', { class: 'zx-record-card__titles' }, titleRow,
|
|
131
148
|
isEmptyContent(subtitle) ? null : h('p', { class: 'zx-record-card__subtitle' }, contentNode(subtitle))),
|
|
132
149
|
actionArea);
|
|
133
|
-
const metadata =
|
|
134
|
-
const body = h('div', { class: 'zx-record-card__body' }, header, metadata);
|
|
150
|
+
const metadata = createFieldList(record, index, fieldGroups.metadata, 'zx-record-card__metadata');
|
|
151
|
+
const body = h('div', { class: 'zx-record-card__body' }, eyebrow, header, metadata);
|
|
135
152
|
const card = /** @type {HTMLLIElement} */ (h('li', {
|
|
136
153
|
class: 'zx-record-card',
|
|
137
154
|
tabindex: 0,
|
|
138
|
-
|
|
155
|
+
ariaDescription: selectable ? selected ? 'Selected' : 'Not selected' : undefined,
|
|
139
156
|
ariaLabelledby: headingId,
|
|
140
157
|
dataset: {
|
|
141
158
|
recordIndex: String(index),
|
|
@@ -146,6 +163,36 @@ export function createRecordCard(record, index, options = {}) {
|
|
|
146
163
|
return card;
|
|
147
164
|
}
|
|
148
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Partitions ordered visible fields into the shared record-card slots. A recognized
|
|
168
|
+
* `field.view.card.slot` moves the field to that semantic region; absent or unknown values remain
|
|
169
|
+
* ordinary metadata. Title and subtitle fields retain the existing no-duplication rule.
|
|
170
|
+
*
|
|
171
|
+
* The input array and descriptors are never mutated. Each field appears in at most one result
|
|
172
|
+
* array, and the order within every result follows the supplied visible-field order.
|
|
173
|
+
*
|
|
174
|
+
* @param {ViewField[]} fields Ordered visible fields.
|
|
175
|
+
* @param {RecordCardValueSource} [titleSource=null] Active title field or resolver.
|
|
176
|
+
* @param {RecordCardValueSource} [subtitleSource=null] Active subtitle field or resolver.
|
|
177
|
+
* @returns {RecordCardFieldGroups} Partitioned fields.
|
|
178
|
+
*/
|
|
179
|
+
export function partitionRecordCardFields(fields, titleSource = null, subtitleSource = null) {
|
|
180
|
+
/** @type {RecordCardFieldGroups} */
|
|
181
|
+
const groups = { eyebrowStart: [], eyebrowEnd: [], titlePrefix: [], metadata: [] };
|
|
182
|
+
const titleId = typeof titleSource === 'string' ? titleSource : null;
|
|
183
|
+
const subtitleId = typeof subtitleSource === 'string' ? subtitleSource : null;
|
|
184
|
+
|
|
185
|
+
for (const field of Array.isArray(fields) ? fields : []) {
|
|
186
|
+
if (!field.duplicate && (field.id === titleId || field.id === subtitleId)) continue;
|
|
187
|
+
const slot = resolveRecordCardFieldSlot(field);
|
|
188
|
+
if (slot === 'eyebrow-start') groups.eyebrowStart.push(field);
|
|
189
|
+
else if (slot === 'eyebrow-end') groups.eyebrowEnd.push(field);
|
|
190
|
+
else if (slot === 'title-prefix') groups.titlePrefix.push(field);
|
|
191
|
+
else groups.metadata.push(field);
|
|
192
|
+
}
|
|
193
|
+
return groups;
|
|
194
|
+
}
|
|
195
|
+
|
|
149
196
|
/**
|
|
150
197
|
* Resolves a preview without touching the DOM. Unsafe, malformed, and non-image URL schemes are
|
|
151
198
|
* rejected into the same descriptor shape so callers can still render the stable fallback.
|
|
@@ -245,26 +292,52 @@ function createPreview(preview) {
|
|
|
245
292
|
}, fallback, image);
|
|
246
293
|
}
|
|
247
294
|
|
|
295
|
+
/**
|
|
296
|
+
* @param {ViewRecord} record
|
|
297
|
+
* @param {number} index
|
|
298
|
+
* @param {ViewField[]} eyebrowStart
|
|
299
|
+
* @param {ViewField[]} eyebrowEnd
|
|
300
|
+
* @returns {HTMLElement|null}
|
|
301
|
+
*/
|
|
302
|
+
function createEyebrow(record, index, eyebrowStart, eyebrowEnd) {
|
|
303
|
+
const start = createFieldList(record, index, eyebrowStart,
|
|
304
|
+
'zx-record-card__eyebrow-group', 'eyebrow-start');
|
|
305
|
+
const end = createFieldList(record, index, eyebrowEnd,
|
|
306
|
+
'zx-record-card__eyebrow-group', 'eyebrow-end');
|
|
307
|
+
return start || end ? h('div', { class: 'zx-record-card__eyebrow' }, start, end) : null;
|
|
308
|
+
}
|
|
309
|
+
|
|
248
310
|
/**
|
|
249
311
|
* @param {ViewRecord} record
|
|
250
312
|
* @param {number} index
|
|
251
313
|
* @param {ViewField[]} fields
|
|
252
|
-
* @param {
|
|
253
|
-
* @param {
|
|
314
|
+
* @param {string} className
|
|
315
|
+
* @param {RecordCardFieldSlot|null} [slot=null]
|
|
254
316
|
* @returns {HTMLElement|null}
|
|
255
317
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
318
|
+
function createFieldList(record, index, fields, className, slot = null) {
|
|
319
|
+
if (fields.length === 0) return null;
|
|
320
|
+
return h('dl', {
|
|
321
|
+
class: className,
|
|
322
|
+
dataset: slot ? { cardSlot: slot } : undefined
|
|
323
|
+
}, fields.map((field) => h('div', {
|
|
262
324
|
class: 'zx-record-card__field',
|
|
263
|
-
dataset: { fieldId: field.id }
|
|
325
|
+
dataset: { fieldId: field.id, ...(slot ? { cardSlot: slot } : {}) }
|
|
264
326
|
}, h('dt', { class: 'zx-record-card__label' }, field.label),
|
|
265
327
|
h('dd', { class: 'zx-record-card__value' }, contentNode(renderViewField(field, record, index))))));
|
|
266
328
|
}
|
|
267
329
|
|
|
330
|
+
/** @param {ViewField} field @returns {RecordCardFieldSlot|null} */
|
|
331
|
+
function resolveRecordCardFieldSlot(field) {
|
|
332
|
+
const view = field?.view;
|
|
333
|
+
if (!view || typeof view !== 'object' || Array.isArray(view)) return null;
|
|
334
|
+
const card = view.card;
|
|
335
|
+
if (!card || typeof card !== 'object' || Array.isArray(card)) return null;
|
|
336
|
+
const slot = /** @type {{slot?:unknown}} */ (card).slot;
|
|
337
|
+
return slot === 'eyebrow-start' || slot === 'eyebrow-end' || slot === 'title-prefix'
|
|
338
|
+
? slot : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
268
341
|
/** @param {RecordCardAction} action @returns {HTMLElement} */
|
|
269
342
|
function createAction(action) {
|
|
270
343
|
const label = String(action.label ?? '');
|
|
@@ -356,7 +429,7 @@ function safePreviewUrl(value) {
|
|
|
356
429
|
try {
|
|
357
430
|
const base = globalThis.location?.href ?? 'https://zx.invalid/';
|
|
358
431
|
const protocol = new URL(href, base).protocol.toLowerCase();
|
|
359
|
-
return ['http:', 'https:', 'blob:'
|
|
432
|
+
return ['http:', 'https:', 'blob:'].includes(protocol) ? href : null;
|
|
360
433
|
} catch {
|
|
361
434
|
return null;
|
|
362
435
|
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
.zx-context-menu__item:hover,
|
|
31
31
|
.zx-context-menu__item:focus-visible {
|
|
32
|
-
background: var(--zx-color-
|
|
32
|
+
background: var(--zx-color-overlay-hover);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.zx-context-menu__item[data-danger="true"] {
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
.zx-context-menu__separator {
|
|
51
51
|
block-size: 1px;
|
|
52
52
|
margin: var(--zx-space-1) var(--zx-space-2);
|
|
53
|
-
background: var(--zx-color-
|
|
53
|
+
background: var(--zx-color-overlay-divider);
|
|
54
54
|
}
|
|
@@ -82,7 +82,11 @@ export class ContextMenu extends Component {
|
|
|
82
82
|
this.#target = resolved;
|
|
83
83
|
document.body.append(this.el);
|
|
84
84
|
|
|
85
|
-
this.#dropdown = new Dropdown(this.el, {
|
|
85
|
+
this.#dropdown = new Dropdown(this.el, {
|
|
86
|
+
openOn: 'manual',
|
|
87
|
+
placement: 'bottom-start',
|
|
88
|
+
scope: resolved
|
|
89
|
+
});
|
|
86
90
|
this.#panel = this.#dropdown.getPanel();
|
|
87
91
|
this.#panel.classList.add('zx-context-menu');
|
|
88
92
|
this.#panel.setAttribute('role', 'menu');
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
place-items: center;
|
|
32
32
|
min-inline-size: var(--zx-control-height);
|
|
33
33
|
padding: 0;
|
|
34
|
-
border-color:
|
|
35
|
-
background:
|
|
34
|
+
border-color: transparent;
|
|
35
|
+
background: transparent;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
.zx-date-picker__heading {
|
|
39
39
|
overflow: hidden;
|
|
40
|
-
border-color:
|
|
41
|
-
background:
|
|
40
|
+
border-color: transparent;
|
|
41
|
+
background: transparent;
|
|
42
42
|
font-weight: 650;
|
|
43
43
|
text-overflow: ellipsis;
|
|
44
44
|
text-wrap: nowrap;
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
min-inline-size: 0;
|
|
77
77
|
min-block-size: var(--zx-control-height);
|
|
78
78
|
padding: 0;
|
|
79
|
-
border-color:
|
|
79
|
+
border-color: transparent;
|
|
80
80
|
border-radius: var(--zx-radius-md);
|
|
81
|
-
background:
|
|
81
|
+
background: transparent;
|
|
82
82
|
font-variant-numeric: tabular-nums;
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -125,10 +125,12 @@
|
|
|
125
125
|
inset: var(--zx-space-3);
|
|
126
126
|
border: 1px solid var(--zx-color-overlay-border);
|
|
127
127
|
border-radius: var(--zx-radius-md);
|
|
128
|
-
background: var(--zx-color-overlay-surface);
|
|
128
|
+
background-color: var(--zx-color-overlay-surface);
|
|
129
|
+
background-image: var(--zx-overlay-surface-image);
|
|
130
|
+
background-clip: padding-box;
|
|
129
131
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
130
132
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
131
|
-
box-shadow: var(--zx-glass-
|
|
133
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
132
134
|
padding: var(--zx-space-3);
|
|
133
135
|
}
|
|
134
136
|
|
|
@@ -157,11 +159,16 @@
|
|
|
157
159
|
.zx-month-picker__month {
|
|
158
160
|
min-inline-size: 0;
|
|
159
161
|
padding-inline: var(--zx-space-1);
|
|
160
|
-
border-color:
|
|
161
|
-
background:
|
|
162
|
+
border-color: transparent;
|
|
163
|
+
background: transparent;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.zx-date-picker__quick-month[aria-selected="true"] {
|
|
167
|
+
background: var(--zx-color-overlay-selected);
|
|
168
|
+
color: var(--zx-color-accent);
|
|
169
|
+
font-weight: 700;
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
.zx-date-picker__quick-month[aria-selected="true"],
|
|
165
172
|
.zx-month-picker__month[aria-selected="true"] {
|
|
166
173
|
background: var(--zx-color-bg-selected);
|
|
167
174
|
color: var(--zx-color-accent);
|
|
@@ -124,10 +124,12 @@
|
|
|
124
124
|
.zx-date-range-box__popover .zx-date-range-picker__surface {
|
|
125
125
|
max-inline-size: none;
|
|
126
126
|
border-color: var(--zx-color-overlay-border);
|
|
127
|
-
background: var(--zx-color-overlay-surface);
|
|
127
|
+
background-color: var(--zx-color-overlay-surface);
|
|
128
|
+
background-image: var(--zx-overlay-surface-image);
|
|
129
|
+
background-clip: padding-box;
|
|
128
130
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
129
131
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
130
|
-
box-shadow: var(--zx-glass-
|
|
132
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
@media (prefers-reduced-transparency: reduce) {
|
|
@@ -104,10 +104,12 @@
|
|
|
104
104
|
|
|
105
105
|
.zx-datebox__popover .zx-date-picker__surface {
|
|
106
106
|
border-color: var(--zx-color-overlay-border);
|
|
107
|
-
background: var(--zx-color-overlay-surface);
|
|
107
|
+
background-color: var(--zx-color-overlay-surface);
|
|
108
|
+
background-image: var(--zx-overlay-surface-image);
|
|
109
|
+
background-clip: padding-box;
|
|
108
110
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
109
111
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
110
|
-
box-shadow: var(--zx-glass-
|
|
112
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
@media (prefers-reduced-transparency: reduce) {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
padding: 0;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
.zx-dialog[open]
|
|
6
|
+
.zx-dialog[open],
|
|
7
|
+
.zx-dialog:popover-open {
|
|
7
8
|
display: grid;
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
|
|
18
19
|
.zx-dialog__header {
|
|
19
20
|
justify-content: space-between;
|
|
20
|
-
border-block-end: 1px solid var(--zx-color-
|
|
21
|
+
border-block-end: 1px solid var(--zx-color-overlay-divider);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
.zx-dialog__title {
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
|
|
41
42
|
.zx-dialog__footer {
|
|
42
43
|
justify-content: flex-end;
|
|
43
|
-
border-block-start: 1px solid var(--zx-color-
|
|
44
|
+
border-block-start: 1px solid var(--zx-color-overlay-divider);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
.zx-dialog__footer[hidden] {
|
|
@@ -22,6 +22,7 @@ const FOCUSABLE_SELECTOR = [
|
|
|
22
22
|
* @property {DialogButton[]} [buttons=[]] Footer buttons.
|
|
23
23
|
* @property {boolean} [closable=true] Whether Escape and the header button may close the dialog.
|
|
24
24
|
* @property {Node|string|number|{toElement: () => Node|null}|null} [content=null] Body content.
|
|
25
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the overlay; defaults to the opener.
|
|
25
26
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open event listener.
|
|
26
27
|
* @property {(event: CustomEvent<{result: unknown}>) => void} [onclose] Close event listener.
|
|
27
28
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [oncancel] Cancel event listener.
|
|
@@ -89,7 +90,7 @@ export class Dialog extends Modal {
|
|
|
89
90
|
#opener = null;
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
|
-
* Creates a structured dialog
|
|
93
|
+
* Creates a structured dialog in the configured theme scope, or at document level when unscoped.
|
|
93
94
|
* @param {Element|string|null} [_target=null] Ignored; Dialog always owns its root.
|
|
94
95
|
* @param {DialogOptions} [options={}] Dialog options.
|
|
95
96
|
*/
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
overflow: auto;
|
|
5
5
|
border: 1px solid var(--zx-color-overlay-border);
|
|
6
6
|
border-radius: var(--zx-radius-md);
|
|
7
|
-
background: var(--zx-color-overlay-surface);
|
|
7
|
+
background-color: var(--zx-color-overlay-surface);
|
|
8
|
+
background-image: var(--zx-overlay-surface-image);
|
|
9
|
+
background-clip: padding-box;
|
|
8
10
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
9
11
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
10
|
-
box-shadow: var(--zx-glass-
|
|
12
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
11
13
|
color: var(--zx-color-text);
|
|
12
14
|
padding: var(--zx-space-3);
|
|
13
15
|
}
|