@ketvietlab/design-system 0.1.25 → 0.1.30
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/README.md +130 -5
- package/dist/atlas/island-runtime.d.mts +2 -0
- package/dist/atlas/island-runtime.d.mts.map +1 -0
- package/dist/atlas/island-runtime.mjs +2 -0
- package/dist/atlas/island-runtime.mjs.map +1 -0
- package/dist/atlas/relation-select-hydration.d.ts +2 -0
- package/dist/atlas/relation-select-hydration.d.ts.map +1 -0
- package/dist/atlas/relation-select-hydration.js +40 -0
- package/dist/atlas/relation-select-hydration.js.map +1 -0
- package/dist/atlas-cli.d.ts.map +1 -1
- package/dist/atlas-cli.js +28 -10
- package/dist/atlas-cli.js.map +1 -1
- package/dist/catalogue/date-pickers.d.ts +6 -0
- package/dist/catalogue/date-pickers.d.ts.map +1 -0
- package/dist/catalogue/date-pickers.js +16 -0
- package/dist/catalogue/date-pickers.js.map +1 -0
- package/dist/catalogue/index.d.ts +7 -2
- package/dist/catalogue/index.d.ts.map +1 -1
- package/dist/catalogue/index.js +20 -4
- package/dist/catalogue/index.js.map +1 -1
- package/dist/catalogue/inventory-policy.json +49 -0
- package/dist/catalogue/inventory.generated.js +1 -1
- package/dist/catalogue/inventory.generated.js.map +1 -1
- package/dist/catalogue/inventory.generated.json +2815 -1108
- package/dist/catalogue/layering.d.ts +10 -0
- package/dist/catalogue/layering.d.ts.map +1 -0
- package/dist/catalogue/layering.js +67 -0
- package/dist/catalogue/layering.js.map +1 -0
- package/dist/catalogue/primitives.css +77 -0
- package/dist/catalogue/primitives.d.ts +34 -0
- package/dist/catalogue/primitives.d.ts.map +1 -0
- package/dist/catalogue/primitives.js +149 -0
- package/dist/catalogue/primitives.js.map +1 -0
- package/dist/catalogue/registry.d.ts.map +1 -1
- package/dist/catalogue/registry.js +18 -0
- package/dist/catalogue/registry.js.map +1 -1
- package/dist/catalogue/specimens.d.ts.map +1 -1
- package/dist/catalogue/specimens.js +142 -22
- package/dist/catalogue/specimens.js.map +1 -1
- package/dist/catalogue/styles.css +1 -0
- package/dist/contract/index.d.ts +2 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +11 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/layout-audit.d.ts +32 -0
- package/dist/contract/layout-audit.d.ts.map +1 -0
- package/dist/contract/layout-audit.js +204 -0
- package/dist/contract/layout-audit.js.map +1 -0
- package/dist/forms/date-time/date-math.d.ts +23 -0
- package/dist/forms/date-time/date-math.d.ts.map +1 -0
- package/dist/forms/date-time/date-math.js +83 -0
- package/dist/forms/date-time/date-math.js.map +1 -0
- package/dist/forms/date-time/index.d.ts +37 -6
- package/dist/forms/date-time/index.d.ts.map +1 -1
- package/dist/forms/date-time/index.js +140 -6
- package/dist/forms/date-time/index.js.map +1 -1
- package/dist/forms/date-time/runtime.d.ts +4 -0
- package/dist/forms/date-time/runtime.d.ts.map +1 -0
- package/dist/forms/date-time/runtime.js +637 -0
- package/dist/forms/date-time/runtime.js.map +1 -0
- package/dist/forms/date-time/styles.css +251 -12
- package/dist/foundations/tokens.css +48 -28
- package/dist/index.d.ts +15 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/interactions/ket-table/demo.d.ts +4 -0
- package/dist/interactions/ket-table/demo.d.ts.map +1 -0
- package/dist/interactions/ket-table/demo.js +147 -0
- package/dist/interactions/ket-table/demo.js.map +1 -0
- package/dist/interactions/ket-table/index.d.ts +199 -0
- package/dist/interactions/ket-table/index.d.ts.map +1 -0
- package/dist/interactions/ket-table/index.js +376 -0
- package/dist/interactions/ket-table/index.js.map +1 -0
- package/dist/interactions/ket-table/styles.css +282 -0
- package/dist/interactions/lightbox/demo.d.ts +3 -0
- package/dist/interactions/lightbox/demo.d.ts.map +1 -0
- package/dist/interactions/lightbox/demo.js +21 -0
- package/dist/interactions/lightbox/demo.js.map +1 -0
- package/dist/interactions/lightbox/index.d.ts +58 -0
- package/dist/interactions/lightbox/index.d.ts.map +1 -0
- package/dist/interactions/lightbox/index.js +220 -0
- package/dist/interactions/lightbox/index.js.map +1 -0
- package/dist/interactions/lightbox/styles.css +153 -0
- package/dist/interactions/relation-select/demo.d.ts +3 -0
- package/dist/interactions/relation-select/demo.d.ts.map +1 -0
- package/dist/interactions/relation-select/demo.js +36 -0
- package/dist/interactions/relation-select/demo.js.map +1 -0
- package/dist/interactions/relation-select/index.d.ts +87 -0
- package/dist/interactions/relation-select/index.d.ts.map +1 -0
- package/dist/interactions/relation-select/index.js +307 -0
- package/dist/interactions/relation-select/index.js.map +1 -0
- package/dist/interactions/relation-select/styles.css +299 -0
- package/dist/interactions/reorder-list/index.d.ts +23 -0
- package/dist/interactions/reorder-list/index.d.ts.map +1 -0
- package/dist/interactions/reorder-list/index.js +15 -0
- package/dist/interactions/reorder-list/index.js.map +1 -0
- package/dist/interactions/reorder-list/runtime.d.ts +5 -0
- package/dist/interactions/reorder-list/runtime.d.ts.map +1 -0
- package/dist/interactions/reorder-list/runtime.js +85 -0
- package/dist/interactions/reorder-list/runtime.js.map +1 -0
- package/dist/interactions/reorder-list/styles.css +33 -0
- package/dist/interactions/search-filter/demo.d.ts +3 -0
- package/dist/interactions/search-filter/demo.d.ts.map +1 -0
- package/dist/interactions/search-filter/demo.js +74 -0
- package/dist/interactions/search-filter/demo.js.map +1 -0
- package/dist/interactions/search-filter/index.d.ts +153 -0
- package/dist/interactions/search-filter/index.d.ts.map +1 -0
- package/dist/interactions/search-filter/index.js +824 -0
- package/dist/interactions/search-filter/index.js.map +1 -0
- package/dist/interactions/search-filter/styles.css +568 -0
- package/dist/layout-audit-cli.d.ts +7 -0
- package/dist/layout-audit-cli.d.ts.map +1 -0
- package/dist/layout-audit-cli.js +63 -0
- package/dist/layout-audit-cli.js.map +1 -0
- package/dist/layouts/app-navigation/index.d.ts +11 -5
- package/dist/layouts/app-navigation/index.d.ts.map +1 -1
- package/dist/layouts/app-navigation/index.js +5 -3
- package/dist/layouts/app-navigation/index.js.map +1 -1
- package/dist/layouts/app-navigation/styles.css +47 -0
- package/dist/layouts/grouped/styles.css +5 -36
- package/dist/layouts/layering/styles.css +159 -0
- package/dist/layouts/layout/index.d.ts +6 -0
- package/dist/layouts/layout/index.d.ts.map +1 -1
- package/dist/layouts/layout/index.js +6 -1
- package/dist/layouts/layout/index.js.map +1 -1
- package/dist/layouts/layout/styles.css +15 -6
- package/dist/layouts/page-identity/index.d.ts +10 -7
- package/dist/layouts/page-identity/index.d.ts.map +1 -1
- package/dist/layouts/page-identity/index.js +9 -4
- package/dist/layouts/page-identity/index.js.map +1 -1
- package/dist/layouts/shell/index.d.ts +34 -1
- package/dist/layouts/shell/index.d.ts.map +1 -1
- package/dist/layouts/shell/index.js +26 -3
- package/dist/layouts/shell/index.js.map +1 -1
- package/dist/layouts/shell/styles.css +284 -2
- package/dist/patterns/board-page/index.d.ts +1 -1
- package/dist/patterns/board-page/index.d.ts.map +1 -1
- package/dist/patterns/board-page/index.js +0 -1
- package/dist/patterns/board-page/index.js.map +1 -1
- package/dist/patterns/board-page/styles.css +0 -12
- package/dist/patterns/dashboard-page/index.d.ts +1 -1
- package/dist/patterns/dashboard-page/index.d.ts.map +1 -1
- package/dist/patterns/dashboard-page/index.js +0 -1
- package/dist/patterns/dashboard-page/index.js.map +1 -1
- package/dist/patterns/dashboard-page/styles.css +0 -13
- package/dist/patterns/data-table/styles.css +1 -1
- package/dist/patterns/form-page/index.d.ts +1 -1
- package/dist/patterns/form-page/index.d.ts.map +1 -1
- package/dist/patterns/form-page/index.js +0 -1
- package/dist/patterns/form-page/index.js.map +1 -1
- package/dist/patterns/form-page/styles.css +0 -12
- package/dist/patterns/list-page/index.d.ts +10 -4
- package/dist/patterns/list-page/index.d.ts.map +1 -1
- package/dist/patterns/list-page/index.js +10 -8
- package/dist/patterns/list-page/index.js.map +1 -1
- package/dist/patterns/list-page/responsive.css +1 -13
- package/dist/patterns/list-page/styles.css +20 -20
- package/dist/patterns/modal-sheet/index.d.ts +2 -0
- package/dist/patterns/modal-sheet/index.d.ts.map +1 -1
- package/dist/patterns/modal-sheet/index.js +3 -1
- package/dist/patterns/modal-sheet/index.js.map +1 -1
- package/dist/patterns/modal-sheet/styles.css +1 -1
- package/dist/patterns/page-shell/styles.css +0 -12
- package/dist/patterns/record-form/styles.css +34 -0
- package/dist/patterns/record-page/index.d.ts +1 -2
- package/dist/patterns/record-page/index.d.ts.map +1 -1
- package/dist/patterns/record-page/index.js +2 -3
- package/dist/patterns/record-page/index.js.map +1 -1
- package/dist/patterns/record-page/styles.css +0 -12
- package/dist/patterns/workspace-page/index.d.ts +1 -2
- package/dist/patterns/workspace-page/index.d.ts.map +1 -1
- package/dist/patterns/workspace-page/index.js +0 -1
- package/dist/patterns/workspace-page/index.js.map +1 -1
- package/dist/primitives/actions/index.d.ts +4 -0
- package/dist/primitives/actions/index.d.ts.map +1 -1
- package/dist/primitives/actions/index.js +2 -2
- package/dist/primitives/actions/index.js.map +1 -1
- package/dist/primitives/actions/styles.css +3 -1
- package/dist/primitives/feedback/styles.css +11 -6
- package/dist/primitives/field/index.d.ts +5 -0
- package/dist/primitives/field/index.d.ts.map +1 -1
- package/dist/primitives/field/index.js +1 -1
- package/dist/primitives/field/index.js.map +1 -1
- package/dist/primitives/field/native-control.d.ts.map +1 -1
- package/dist/primitives/field/native-control.js +3 -3
- package/dist/primitives/field/native-control.js.map +1 -1
- package/dist/primitives/field/styles.css +124 -2
- package/dist/primitives/progress/styles.css +3 -0
- package/dist/primitives/status/index.d.ts +12 -1
- package/dist/primitives/status/index.d.ts.map +1 -1
- package/dist/primitives/status/index.js +16 -1
- package/dist/primitives/status/index.js.map +1 -1
- package/dist/primitives/status/styles.css +34 -2
- package/dist/record/display/index.d.ts +6 -0
- package/dist/record/display/index.d.ts.map +1 -1
- package/dist/record/display/index.js +6 -1
- package/dist/record/display/index.js.map +1 -1
- package/dist/record/display/styles.css +25 -0
- package/dist/record/formatted-values/index.d.ts +2 -1
- package/dist/record/formatted-values/index.d.ts.map +1 -1
- package/dist/record/formatted-values/index.js +2 -1
- package/dist/record/formatted-values/index.js.map +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +132 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
@layer ket.app {
|
|
2
|
+
/* A Fancybox-style viewer: thumbnails open a full-screen stage above every
|
|
3
|
+
other layer, including a record modal, with zoom, pan and previous/next. */
|
|
4
|
+
:where([data-kv-design-system]) [data-ui="lightbox"] {
|
|
5
|
+
display: contents;
|
|
6
|
+
}
|
|
7
|
+
:where([data-kv-design-system]) [data-ui="lightbox-thumbs"] {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
gap: var(--kv-space-2);
|
|
11
|
+
}
|
|
12
|
+
:where([data-kv-design-system]) [data-ui="lightbox-thumb"],
|
|
13
|
+
:where([data-kv-design-system]) [data-ui="lightbox-empty"] {
|
|
14
|
+
--lightbox-thumb-size: 5.5rem;
|
|
15
|
+
display: inline-grid;
|
|
16
|
+
place-items: center;
|
|
17
|
+
inline-size: var(--lightbox-thumb-size);
|
|
18
|
+
block-size: var(--lightbox-thumb-size);
|
|
19
|
+
flex: none;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
padding: 0;
|
|
22
|
+
border: 1px solid var(--kv-border-default);
|
|
23
|
+
border-radius: var(--kv-radius-md);
|
|
24
|
+
background: var(--kv-surface-raised);
|
|
25
|
+
color: var(--kv-text-muted);
|
|
26
|
+
font-size: var(--kv-text-sm);
|
|
27
|
+
text-align: center;
|
|
28
|
+
}
|
|
29
|
+
:where([data-kv-design-system]) [data-ui="lightbox-thumb"] {
|
|
30
|
+
cursor: zoom-in;
|
|
31
|
+
}
|
|
32
|
+
:where([data-kv-design-system]) [data-ui="lightbox-thumb"]:focus-visible {
|
|
33
|
+
outline: 2px solid var(--kv-focus-border);
|
|
34
|
+
outline-offset: 2px;
|
|
35
|
+
}
|
|
36
|
+
:where([data-kv-design-system]) [data-ui="lightbox-thumb"] img {
|
|
37
|
+
inline-size: 100%;
|
|
38
|
+
block-size: 100%;
|
|
39
|
+
object-fit: cover;
|
|
40
|
+
}
|
|
41
|
+
:where([data-kv-design-system])
|
|
42
|
+
:is([data-ui="lightbox-thumb"], [data-ui="lightbox-empty"])[data-size="small"] {
|
|
43
|
+
--lightbox-thumb-size: 2.5rem;
|
|
44
|
+
border-radius: var(--kv-radius-sm);
|
|
45
|
+
}
|
|
46
|
+
:where([data-kv-design-system])
|
|
47
|
+
:is([data-ui="lightbox-thumb"], [data-ui="lightbox-empty"])[data-size="large"] {
|
|
48
|
+
--lightbox-thumb-size: 9rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:where([data-kv-design-system]) [data-ui="lightbox-layer"] {
|
|
52
|
+
position: fixed;
|
|
53
|
+
inset: 0;
|
|
54
|
+
z-index: calc(var(--kv-z-modal) + 10);
|
|
55
|
+
display: grid;
|
|
56
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
57
|
+
color: var(--kv-panel-bg);
|
|
58
|
+
}
|
|
59
|
+
:where([data-kv-design-system]) [data-ui="lightbox-backdrop"] {
|
|
60
|
+
position: absolute;
|
|
61
|
+
inset: 0;
|
|
62
|
+
background: color-mix(in srgb, var(--kv-text-main) 92%, transparent);
|
|
63
|
+
}
|
|
64
|
+
:where([data-kv-design-system]) [data-ui="lightbox-toolbar"] {
|
|
65
|
+
position: relative;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: flex-end;
|
|
69
|
+
gap: var(--kv-space-1);
|
|
70
|
+
padding: var(--kv-space-2) var(--kv-space-3);
|
|
71
|
+
}
|
|
72
|
+
:where([data-kv-design-system]) [data-ui="lightbox-counter"] {
|
|
73
|
+
margin-inline-end: auto;
|
|
74
|
+
font-size: var(--kv-text-sm);
|
|
75
|
+
font-variant-numeric: tabular-nums;
|
|
76
|
+
}
|
|
77
|
+
:where([data-kv-design-system]) :is([data-ui="lightbox-tool"], [data-ui="lightbox-nav"]) {
|
|
78
|
+
display: inline-grid;
|
|
79
|
+
place-items: center;
|
|
80
|
+
inline-size: 2.5rem;
|
|
81
|
+
block-size: 2.5rem;
|
|
82
|
+
padding: 0;
|
|
83
|
+
border: 0;
|
|
84
|
+
border-radius: 999px;
|
|
85
|
+
background: color-mix(in srgb, var(--kv-panel-bg) 12%, transparent);
|
|
86
|
+
color: inherit;
|
|
87
|
+
font-size: 1.5rem;
|
|
88
|
+
line-height: 1;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
:where([data-kv-design-system]) :is([data-ui="lightbox-tool"], [data-ui="lightbox-nav"]):hover {
|
|
92
|
+
background: color-mix(in srgb, var(--kv-panel-bg) 24%, transparent);
|
|
93
|
+
}
|
|
94
|
+
:where([data-kv-design-system]) :is([data-ui="lightbox-tool"], [data-ui="lightbox-nav"]):focus-visible {
|
|
95
|
+
outline: 2px solid var(--kv-panel-bg);
|
|
96
|
+
outline-offset: 2px;
|
|
97
|
+
}
|
|
98
|
+
:where([data-kv-design-system]) [data-ui="lightbox-tool"]:disabled {
|
|
99
|
+
opacity: 0.4;
|
|
100
|
+
cursor: default;
|
|
101
|
+
}
|
|
102
|
+
:where([data-kv-design-system]) [data-ui="lightbox-stage"] {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: grid;
|
|
105
|
+
place-items: center;
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
padding: var(--kv-space-2) var(--kv-space-12);
|
|
108
|
+
touch-action: none;
|
|
109
|
+
}
|
|
110
|
+
:where([data-kv-design-system]) [data-ui="lightbox-image"] {
|
|
111
|
+
max-inline-size: 100%;
|
|
112
|
+
max-block-size: 100%;
|
|
113
|
+
object-fit: contain;
|
|
114
|
+
transform-origin: center;
|
|
115
|
+
transition: transform 160ms ease-out;
|
|
116
|
+
cursor: zoom-in;
|
|
117
|
+
user-select: none;
|
|
118
|
+
}
|
|
119
|
+
:where([data-kv-design-system]) [data-ui="lightbox-stage"][data-zoomed="true"] [data-ui="lightbox-image"] {
|
|
120
|
+
cursor: grab;
|
|
121
|
+
transition: none;
|
|
122
|
+
}
|
|
123
|
+
:where([data-kv-design-system]) [data-ui="lightbox-nav"] {
|
|
124
|
+
position: absolute;
|
|
125
|
+
inset-block-start: 50%;
|
|
126
|
+
translate: 0 -50%;
|
|
127
|
+
}
|
|
128
|
+
:where([data-kv-design-system]) [data-ui="lightbox-nav"][data-direction="previous"] {
|
|
129
|
+
inset-inline-start: var(--kv-space-3);
|
|
130
|
+
}
|
|
131
|
+
:where([data-kv-design-system]) [data-ui="lightbox-nav"][data-direction="next"] {
|
|
132
|
+
inset-inline-end: var(--kv-space-3);
|
|
133
|
+
}
|
|
134
|
+
:where([data-kv-design-system]) [data-ui="lightbox-caption"] {
|
|
135
|
+
position: relative;
|
|
136
|
+
margin: 0;
|
|
137
|
+
padding: var(--kv-space-2) var(--kv-space-4) var(--kv-space-4);
|
|
138
|
+
font-size: var(--kv-text-sm);
|
|
139
|
+
text-align: center;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (max-width: 47.9375rem) {
|
|
143
|
+
:where([data-kv-design-system]) [data-ui="lightbox-stage"] {
|
|
144
|
+
padding-inline: var(--kv-space-2);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (prefers-reduced-motion: reduce) {
|
|
149
|
+
:where([data-kv-design-system]) [data-ui="lightbox-image"] {
|
|
150
|
+
transition: none;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../../../../../packages/design-system/src/interactions/relation-select/demo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAOvD,eAAO,MAAM,wBAAwB,EAAE,oBA6BtC,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Shared between the design-system's own catalogue specimen and the KetAtlas
|
|
2
|
+
// materializer, so both static demos show exactly the same interaction: a closed
|
|
3
|
+
// island with a real, multi-entry option list a reader can open, search and choose
|
|
4
|
+
// from. There is no `manager`, so search beyond these options, create and remove
|
|
5
|
+
// stay no-ops — neither demo has a backend behind it to call.
|
|
6
|
+
export const relationSelectDemoConfig = {
|
|
7
|
+
name: 'partnerId',
|
|
8
|
+
ariaLabel: 'Partner',
|
|
9
|
+
value: 'an-viet',
|
|
10
|
+
options: [
|
|
11
|
+
{ value: 'an-viet', label: 'An Việt', description: 'Hà Nội' },
|
|
12
|
+
{ value: 'song-xanh', label: 'Sông Xanh', description: 'Đà Nẵng' },
|
|
13
|
+
{ value: 'coop-mart', label: 'Coopmart', description: 'TP. Hồ Chí Minh' },
|
|
14
|
+
],
|
|
15
|
+
labels: {
|
|
16
|
+
choose: 'Choose a partner',
|
|
17
|
+
search: 'Search partners',
|
|
18
|
+
more: 'Browse all',
|
|
19
|
+
noRecords: 'No partners found',
|
|
20
|
+
loading: 'Loading…',
|
|
21
|
+
loadError: 'Could not load partners',
|
|
22
|
+
dialogTitle: 'Choose a partner',
|
|
23
|
+
close: 'Close',
|
|
24
|
+
select: 'Select',
|
|
25
|
+
create: 'Create partner',
|
|
26
|
+
edit: 'Edit',
|
|
27
|
+
save: 'Save',
|
|
28
|
+
cancel: 'Cancel',
|
|
29
|
+
remove: 'Remove',
|
|
30
|
+
confirmRemove: 'Confirm remove',
|
|
31
|
+
retry: 'Retry',
|
|
32
|
+
clear: 'Clear',
|
|
33
|
+
chosen: 'Chosen',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=demo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../../../../../../packages/design-system/src/interactions/relation-select/demo.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,iFAAiF;AACjF,mFAAmF;AACnF,iFAAiF;AACjF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAyB;IAC5D,IAAI,EAAE,WAAW;IACjB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE;QACP,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE;QAC7D,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE;QAClE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE;KAC1E;IACD,MAAM,EAAE;QACN,MAAM,EAAE,kBAAkB;QAC1B,MAAM,EAAE,iBAAiB;QACzB,IAAI,EAAE,YAAY;QAClB,SAAS,EAAE,mBAAmB;QAC9B,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,yBAAyB;QACpC,WAAW,EAAE,kBAAkB;QAC/B,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,gBAAgB;QACxB,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;QAChB,aAAa,EAAE,gBAAgB;QAC/B,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,QAAQ;KACjB;CACF,CAAA"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { IslandController, IslandProps } from '@ketvietlab/ketjs-view';
|
|
2
|
+
export declare const HOOKS: readonly ['relation-select', 'relation-native', 'relation-trigger', 'relation-value', 'relation-caret', 'relation-chips', 'relation-chip', 'relation-chip-clear', 'relation-menu', 'relation-search', 'relation-options', 'relation-option', 'relation-footer', 'relation-empty', 'relation-editor', 'relation-dialog-toolbar', 'relation-search-field', 'relation-search-icon', 'relation-dialog-list', 'relation-dialog-row', 'relation-dialog-main', 'relation-dialog-meta', 'relation-dialog-actions'];
|
|
3
|
+
export type RelationOption = {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type RelationEditorField = {
|
|
9
|
+
name: string;
|
|
10
|
+
label: string;
|
|
11
|
+
type?: 'text' | 'email' | 'tel' | 'select';
|
|
12
|
+
required?: boolean;
|
|
13
|
+
options?: RelationOption[];
|
|
14
|
+
};
|
|
15
|
+
export type RelationManager = {
|
|
16
|
+
listFunction: string;
|
|
17
|
+
listInput?: Record<string, unknown>;
|
|
18
|
+
searchParam?: string;
|
|
19
|
+
limitParam?: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
idField?: string;
|
|
22
|
+
labelField?: string;
|
|
23
|
+
descriptionField?: string;
|
|
24
|
+
saveFunction?: string;
|
|
25
|
+
saveDefaults?: Record<string, unknown>;
|
|
26
|
+
removeFunction?: string;
|
|
27
|
+
removeDefaults?: Record<string, unknown>;
|
|
28
|
+
fields?: RelationEditorField[];
|
|
29
|
+
excludeIds?: string[];
|
|
30
|
+
};
|
|
31
|
+
export type RelationSelectLabels = {
|
|
32
|
+
choose: string;
|
|
33
|
+
search: string;
|
|
34
|
+
more: string;
|
|
35
|
+
noRecords: string;
|
|
36
|
+
loading: string;
|
|
37
|
+
loadError: string;
|
|
38
|
+
dialogTitle: string;
|
|
39
|
+
close: string;
|
|
40
|
+
select: string;
|
|
41
|
+
create: string;
|
|
42
|
+
edit: string;
|
|
43
|
+
save: string;
|
|
44
|
+
cancel: string;
|
|
45
|
+
remove: string;
|
|
46
|
+
confirmRemove: string;
|
|
47
|
+
retry: string;
|
|
48
|
+
clear: string;
|
|
49
|
+
chosen: string;
|
|
50
|
+
};
|
|
51
|
+
export type RelationSelectConfig = {
|
|
52
|
+
name: string;
|
|
53
|
+
ariaLabel: string;
|
|
54
|
+
value?: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Several records under one field name, joined by a comma on the way out.
|
|
57
|
+
*
|
|
58
|
+
* A repeated form key would not survive `readForm`, which builds a
|
|
59
|
+
* `Record<string, string>` and keeps only the last value — so a multi-valued
|
|
60
|
+
* field posts one comma-separated string, which is also what the routes that
|
|
61
|
+
* take value lists already parse.
|
|
62
|
+
*/
|
|
63
|
+
multiple?: boolean;
|
|
64
|
+
values?: string[];
|
|
65
|
+
options: RelationOption[];
|
|
66
|
+
required?: boolean;
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
labels: RelationSelectLabels;
|
|
69
|
+
manager?: RelationManager;
|
|
70
|
+
};
|
|
71
|
+
/** Client-only composition hooks; keep callbacks out of serialized island props. */
|
|
72
|
+
export type RelationSelectCallbacks = {
|
|
73
|
+
options?: () => RelationOption[];
|
|
74
|
+
disabled?: () => boolean;
|
|
75
|
+
onSelect?: (option: RelationOption) => void;
|
|
76
|
+
onCreate?: (query: string) => void;
|
|
77
|
+
/** An add-to-list picker returns to its placeholder after each selection. */
|
|
78
|
+
resetAfterSelect?: boolean;
|
|
79
|
+
};
|
|
80
|
+
type RelationSelectIslandProps = IslandProps & {
|
|
81
|
+
id: string;
|
|
82
|
+
config: RelationSelectConfig;
|
|
83
|
+
};
|
|
84
|
+
export declare function createRelationSelectView(props: RelationSelectIslandProps, callbacks?: RelationSelectCallbacks): IslandController;
|
|
85
|
+
export declare const relationSelect: (props: IslandProps) => IslandController;
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../packages/design-system/src/interactions/relation-select/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@ketvietlab/ketjs-view/jsx-runtime";
|
|
2
|
+
// Unlike its neighbors here, this is a ketjs-view *island*: it owns reactive state
|
|
3
|
+
// (`signal`) and fetches its own data (`/_ket/fn/...`, a ketjs runtime contract, not
|
|
4
|
+
// an application-specific one) rather than being a static, server-rendered control
|
|
5
|
+
// wired up by the shared `attachDesignSystemInteractions` delegator. A consuming app
|
|
6
|
+
// mounts it through its own island runtime (see `defineIsland` in `@ketvietlab/ketjs-view`)
|
|
7
|
+
// and supplies the `manager` config that names which functions to call.
|
|
8
|
+
import { each, effect, signal } from '@ketvietlab/ketjs-view';
|
|
9
|
+
export const HOOKS = [
|
|
10
|
+
'relation-select',
|
|
11
|
+
'relation-native',
|
|
12
|
+
'relation-trigger',
|
|
13
|
+
'relation-value',
|
|
14
|
+
'relation-caret',
|
|
15
|
+
'relation-chips',
|
|
16
|
+
'relation-chip',
|
|
17
|
+
'relation-chip-clear',
|
|
18
|
+
'relation-menu',
|
|
19
|
+
'relation-search',
|
|
20
|
+
'relation-options',
|
|
21
|
+
'relation-option',
|
|
22
|
+
'relation-footer',
|
|
23
|
+
'relation-empty',
|
|
24
|
+
'relation-editor',
|
|
25
|
+
'relation-dialog-toolbar',
|
|
26
|
+
'relation-search-field',
|
|
27
|
+
'relation-search-icon',
|
|
28
|
+
'relation-dialog-list',
|
|
29
|
+
'relation-dialog-row',
|
|
30
|
+
'relation-dialog-main',
|
|
31
|
+
'relation-dialog-meta',
|
|
32
|
+
'relation-dialog-actions',
|
|
33
|
+
];
|
|
34
|
+
const array = (value) => (Array.isArray(value) ? value : []);
|
|
35
|
+
const string = (value) => (value == null ? '' : String(value));
|
|
36
|
+
const callApi = async (name, input, requestSignal) => {
|
|
37
|
+
const response = await fetch(`/_ket/fn/${encodeURIComponent(name)}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
credentials: 'same-origin',
|
|
40
|
+
headers: { 'content-type': 'application/json' },
|
|
41
|
+
body: JSON.stringify(input),
|
|
42
|
+
signal: requestSignal,
|
|
43
|
+
});
|
|
44
|
+
const payload = (await response.json());
|
|
45
|
+
if (!response.ok || payload.ok === false) {
|
|
46
|
+
const domainError = array(payload.errors)[0];
|
|
47
|
+
throw new Error(string(domainError?.message ?? domainError?.code ?? payload.message ?? `HTTP ${response.status}`));
|
|
48
|
+
}
|
|
49
|
+
return payload.value;
|
|
50
|
+
};
|
|
51
|
+
export function createRelationSelectView(props, callbacks = {}) {
|
|
52
|
+
const { id: islandId, config } = props;
|
|
53
|
+
const labels = config.labels;
|
|
54
|
+
const manager = config.manager;
|
|
55
|
+
// One representation for both modes: an array of chosen ids. Single-select is
|
|
56
|
+
// the array of length 0 or 1, so downstream code only branches where the UI
|
|
57
|
+
// genuinely differs — the trigger and the chips.
|
|
58
|
+
const multiple = config.multiple === true;
|
|
59
|
+
const chosen = signal(multiple
|
|
60
|
+
? array(config.values).map(string).filter(Boolean)
|
|
61
|
+
: [string(config.value)].filter(Boolean));
|
|
62
|
+
const selected = () => chosen()[0] ?? '';
|
|
63
|
+
const isChosen = (value) => chosen().includes(string(value));
|
|
64
|
+
const options = signal(array(config.options));
|
|
65
|
+
const availableOptions = () => callbacks.options?.() ?? options();
|
|
66
|
+
const disabled = () => callbacks.disabled?.() ?? config.disabled === true;
|
|
67
|
+
const open = signal(false);
|
|
68
|
+
const dialog = signal(false);
|
|
69
|
+
const query = signal('');
|
|
70
|
+
const rows = signal([]);
|
|
71
|
+
const loading = signal(false);
|
|
72
|
+
const error = signal('');
|
|
73
|
+
const editor = signal(null);
|
|
74
|
+
const pendingRemove = signal('');
|
|
75
|
+
let activeRequest = null;
|
|
76
|
+
let searchTimer;
|
|
77
|
+
let disposed = false;
|
|
78
|
+
const rowId = (row) => string(row[manager?.idField ?? 'id']);
|
|
79
|
+
const rowLabel = (row) => string(row[manager?.labelField ?? 'name']) || labels.noRecords;
|
|
80
|
+
const rowDescription = (row) => string(row[manager?.descriptionField ?? 'ref']);
|
|
81
|
+
const selectedLabel = () => options().find((entry) => string(entry.value) === selected())?.label ?? labels.choose;
|
|
82
|
+
const filteredOptions = () => {
|
|
83
|
+
const needle = query().trim().toLocaleLowerCase();
|
|
84
|
+
const held = needle
|
|
85
|
+
? availableOptions().filter((entry) => `${string(entry.label)} ${string(entry.description)}`.toLocaleLowerCase().includes(needle))
|
|
86
|
+
: availableOptions();
|
|
87
|
+
return callbacks.options ? held : held.slice(0, 7);
|
|
88
|
+
};
|
|
89
|
+
const choose = (value, label, description = '') => {
|
|
90
|
+
if (disabled())
|
|
91
|
+
return;
|
|
92
|
+
const id = string(value);
|
|
93
|
+
if (!options().some((entry) => string(entry.value) === id))
|
|
94
|
+
options.set([...options(), { value: id, label: string(label), description: string(description) }]);
|
|
95
|
+
if (multiple) {
|
|
96
|
+
chosen.set(isChosen(id) ? chosen().filter((held) => held !== id) : [...chosen(), id]);
|
|
97
|
+
callbacks.onSelect?.({ value: id, label: string(label), description: string(description) });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
chosen.set(id ? [id] : []);
|
|
101
|
+
open.set(false);
|
|
102
|
+
dialog.set(false);
|
|
103
|
+
query.set('');
|
|
104
|
+
editor.set(null);
|
|
105
|
+
pendingRemove.set('');
|
|
106
|
+
if (callbacks.resetAfterSelect)
|
|
107
|
+
chosen.set([]);
|
|
108
|
+
callbacks.onSelect?.({ value: id, label: string(label), description: string(description) });
|
|
109
|
+
};
|
|
110
|
+
const unchoose = (value) => {
|
|
111
|
+
chosen.set(chosen().filter((held) => held !== string(value)));
|
|
112
|
+
};
|
|
113
|
+
const labelOf = (value) => options().find((entry) => string(entry.value) === string(value))?.label ?? string(value);
|
|
114
|
+
const loadRows = async () => {
|
|
115
|
+
if (!manager?.listFunction)
|
|
116
|
+
return;
|
|
117
|
+
activeRequest?.abort();
|
|
118
|
+
const request = new AbortController();
|
|
119
|
+
activeRequest = request;
|
|
120
|
+
loading.set(true);
|
|
121
|
+
error.set('');
|
|
122
|
+
try {
|
|
123
|
+
const input = { ...(manager.listInput ?? {}) };
|
|
124
|
+
input[manager.searchParam ?? 'search'] = query().trim();
|
|
125
|
+
input[manager.limitParam ?? 'limit'] = manager.limit ?? 80;
|
|
126
|
+
const value = await callApi(manager.listFunction, input, request.signal);
|
|
127
|
+
const excluded = new Set(array(manager.excludeIds).map(string));
|
|
128
|
+
rows.set(array(value).filter((row) => !excluded.has(rowId(row))));
|
|
129
|
+
}
|
|
130
|
+
catch (caught) {
|
|
131
|
+
if (disposed || (caught instanceof Error && caught.name === 'AbortError'))
|
|
132
|
+
return;
|
|
133
|
+
error.set(caught instanceof Error ? caught.message : labels.loadError);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
if (activeRequest === request)
|
|
137
|
+
loading.set(false);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const openDialog = async () => {
|
|
141
|
+
open.set(false);
|
|
142
|
+
dialog.set(true);
|
|
143
|
+
editor.set(null);
|
|
144
|
+
pendingRemove.set('');
|
|
145
|
+
await loadRows();
|
|
146
|
+
};
|
|
147
|
+
const closeDialog = () => {
|
|
148
|
+
dialog.set(false);
|
|
149
|
+
editor.set(null);
|
|
150
|
+
pendingRemove.set('');
|
|
151
|
+
error.set('');
|
|
152
|
+
};
|
|
153
|
+
const searchManager = (event) => {
|
|
154
|
+
if (!(event.currentTarget instanceof HTMLInputElement))
|
|
155
|
+
return;
|
|
156
|
+
query.set(event.currentTarget.value);
|
|
157
|
+
clearTimeout(searchTimer);
|
|
158
|
+
searchTimer = setTimeout(loadRows, 180);
|
|
159
|
+
};
|
|
160
|
+
const save = async (event) => {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
if (!manager?.saveFunction || !(event.currentTarget instanceof HTMLFormElement))
|
|
163
|
+
return;
|
|
164
|
+
const editing = editor();
|
|
165
|
+
const id = editing?.id || crypto.randomUUID();
|
|
166
|
+
const payload = { ...(manager.saveDefaults ?? {}), id };
|
|
167
|
+
for (const [name, value] of new FormData(event.currentTarget).entries())
|
|
168
|
+
payload[name] = string(value);
|
|
169
|
+
loading.set(true);
|
|
170
|
+
error.set('');
|
|
171
|
+
try {
|
|
172
|
+
await callApi(manager.saveFunction, payload);
|
|
173
|
+
const label = string(payload[manager.labelField ?? 'name']);
|
|
174
|
+
if (!editing?.id) {
|
|
175
|
+
choose(id, label);
|
|
176
|
+
if (multiple) {
|
|
177
|
+
editor.set(null);
|
|
178
|
+
await loadRows();
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
options.set(options().map((entry) => string(entry.value) === id ? { ...entry, label: label || entry.label } : entry));
|
|
183
|
+
editor.set(null);
|
|
184
|
+
await loadRows();
|
|
185
|
+
}
|
|
186
|
+
catch (caught) {
|
|
187
|
+
error.set(caught instanceof Error ? caught.message : labels.loadError);
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
loading.set(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const remove = async (row) => {
|
|
194
|
+
const id = rowId(row);
|
|
195
|
+
if (pendingRemove() !== id) {
|
|
196
|
+
pendingRemove.set(id);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (!manager?.removeFunction)
|
|
200
|
+
return;
|
|
201
|
+
loading.set(true);
|
|
202
|
+
error.set('');
|
|
203
|
+
try {
|
|
204
|
+
await callApi(manager.removeFunction, { ...(manager.removeDefaults ?? {}), id });
|
|
205
|
+
unchoose(id);
|
|
206
|
+
options.set(options().filter((entry) => string(entry.value) !== id));
|
|
207
|
+
pendingRemove.set('');
|
|
208
|
+
await loadRows();
|
|
209
|
+
}
|
|
210
|
+
catch (caught) {
|
|
211
|
+
error.set(caught instanceof Error ? caught.message : labels.loadError);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
loading.set(false);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const editorField = (field) => {
|
|
218
|
+
const held = editor()?.row ?? manager?.saveDefaults ?? {};
|
|
219
|
+
const value = string(held[field.name]);
|
|
220
|
+
const fieldId = `${islandId}-relation-editor-${field.name}`;
|
|
221
|
+
return (_jsxs("label", { "data-ui": "form-field", "data-span": "full", htmlFor: fieldId, children: [_jsxs("span", { "data-ui": "form-label", children: [field.label, field.required ? (_jsxs("span", { "data-ui": "form-required", "aria-hidden": "true", children: [' ', "*"] })) : null] }), field.type === 'select' ? (_jsx("select", { "data-ui": "form-control", id: fieldId, name: field.name, required: field.required === true, children: each(array(field.options), (entry) => entry.value, (entry) => (_jsx("option", { value: entry.value, selected: string(entry.value) === value, children: entry.label }))) })) : (_jsx("input", { "data-ui": "form-control", id: fieldId, type: field.type ?? 'text', name: field.name, value: value, autocomplete: "off", required: field.required === true }))] }));
|
|
222
|
+
};
|
|
223
|
+
const editorForm = () => (_jsxs("form", { "data-ui": "relation-editor", onSubmit: save, children: [_jsx("div", { "data-ui": "form-grid", children: each(array(manager?.fields), (field) => field.name, editorField) }), _jsx("div", { "data-ui": "form-actions", children: _jsxs("div", { "data-ui": "action-group", children: [_jsx("button", { "data-ui": "action", "data-variant": "primary", type: "submit", disabled: loading(), children: labels.save }), _jsx("button", { "data-ui": "action", "data-variant": "tertiary", type: "button", onClick: () => editor.set(null), children: labels.cancel })] }) })] }));
|
|
224
|
+
const managerRow = (row) => {
|
|
225
|
+
const id = rowId(row);
|
|
226
|
+
const description = rowDescription(row);
|
|
227
|
+
return (_jsxs("li", { "data-ui": "relation-dialog-row", "data-selected": String(isChosen(id)), children: [_jsxs("button", { "data-ui": "relation-dialog-main", type: "button", "aria-pressed": multiple ? String(isChosen(id)) : null, onClick: () => choose(id, rowLabel(row), description), children: [_jsx("strong", { children: rowLabel(row) }), description ? _jsx("small", { "data-ui": "relation-dialog-meta", children: description }) : null] }), _jsxs("div", { "data-ui": "relation-dialog-actions", children: [_jsx("button", { "data-ui": "action", "data-size": "compact", "data-variant": isChosen(id) ? 'primary' : 'secondary', type: "button", onClick: () => choose(id, rowLabel(row), description), children: isChosen(id) ? labels.chosen : labels.select }), manager?.saveFunction ? (_jsx("button", { "data-ui": "action", "data-size": "compact", "data-variant": "tertiary", type: "button", onClick: () => editor.set({ id, row }), children: labels.edit })) : null, manager?.removeFunction ? (_jsx("button", { "data-ui": "action", "data-size": "compact", "data-variant": "destructive", type: "button", onClick: () => remove(row), children: pendingRemove() === id ? labels.confirmRemove : labels.remove })) : null] })] }));
|
|
228
|
+
};
|
|
229
|
+
const dialogView = () => (_jsxs("div", { "data-ui": "modal-layer", "data-presentation": "dialog", children: [_jsx("button", { "data-ui": "modal-backdrop", type: "button", "aria-label": labels.close, onClick: closeDialog, children: _jsx("span", { children: labels.close }) }), _jsxs("section", { "data-ui": "modal-sheet", role: "dialog", "aria-modal": "true", "aria-labelledby": `${islandId}-relation-title`, children: [_jsxs("header", { "data-ui": "modal-head", children: [_jsx("h2", { "data-ui": "modal-title", id: `${islandId}-relation-title`, children: labels.dialogTitle }), _jsx("button", { "data-ui": "modal-close", type: "button", "aria-label": labels.close, title: labels.close, onClick: closeDialog, children: "\u00D7" })] }), _jsxs("div", { "data-ui": "modal-body", children: [_jsxs("div", { "data-ui": "relation-dialog-toolbar", children: [_jsxs("div", { "data-ui": "relation-search-field", children: [_jsx("span", { "data-ui": "relation-search-icon", "aria-hidden": "true", children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.3-4.3" })] }) }), _jsx("input", { "data-ui": "field-control", type: "search", value: query(), autocomplete: "off", placeholder: labels.search, "aria-label": labels.search, onInput: searchManager })] }), manager?.saveFunction ? (_jsx("button", { "data-ui": "action", "data-variant": "primary", type: "button", onClick: () => editor.set({ id: '', row: manager.saveDefaults ?? {} }), children: labels.create })) : null] }), error() ? (_jsxs("aside", { "data-ui": "notice", "data-tone": "danger", role: "alert", children: [_jsxs("div", { "data-ui": "notice-copy", children: [_jsx("p", { "data-ui": "notice-title", children: labels.loadError }), _jsx("p", { "data-ui": "notice-message", children: error() })] }), _jsx("button", { "data-ui": "action", "data-variant": "secondary", type: "button", onClick: loadRows, children: labels.retry })] })) : null, editor() ? editorForm() : null, loading() ? (_jsx("p", { "data-ui": "relation-empty", role: "status", children: labels.loading })) : null, !loading() && !editor() ? (rows().length ? (_jsx("ul", { "data-ui": "relation-dialog-list", children: each(rows(), rowId, managerRow) })) : (_jsx("p", { "data-ui": "relation-empty", children: labels.noRecords }))) : null] })] })] }));
|
|
230
|
+
const handleKeydown = (event) => {
|
|
231
|
+
if (!(event instanceof KeyboardEvent) || event.key !== 'Escape')
|
|
232
|
+
return;
|
|
233
|
+
if (!open() && !dialog())
|
|
234
|
+
return;
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
event.stopPropagation();
|
|
237
|
+
const trigger = event.currentTarget?.querySelector('[data-ui="relation-trigger"]');
|
|
238
|
+
open.set(false);
|
|
239
|
+
closeDialog();
|
|
240
|
+
trigger?.focus();
|
|
241
|
+
};
|
|
242
|
+
const handleInvalid = (event) => {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
open.set(true);
|
|
245
|
+
if (event.currentTarget instanceof HTMLSelectElement)
|
|
246
|
+
event.currentTarget.nextElementSibling?.focus();
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
view: () => (_jsxs("div", { "data-ui": "relation-select", role: "group", "aria-label": config.ariaLabel, onKeydown: handleKeydown, children: [_jsxs("select", { "data-ui": "relation-native", name: config.name, required: config.required === true, disabled: disabled(), tabindex: "-1", "aria-hidden": "true", onInvalid: handleInvalid, children: [_jsx("option", { value: "", selected: !chosen().length }), multiple ? (chosen().length ? (_jsx("option", { value: chosen().join(','), selected: true, children: chosen().map(labelOf).join(', ') })) : null) : (each(options().filter((entry) => string(entry.value)), (entry) => entry.value, (entry) => (_jsx("option", { value: entry.value, selected: string(entry.value) === selected(), children: entry.label }))))] }), _jsxs("button", { "data-ui": "relation-trigger", "data-empty": String(!chosen().length), type: "button", "aria-haspopup": "listbox", "aria-label": config.ariaLabel, "aria-expanded": String(open()), "aria-controls": `${islandId}-relation-menu`, disabled: disabled(), onClick: () => open.set(!open()), children: [_jsx("span", { "data-ui": "relation-value", children: multiple ? labels.choose : selectedLabel() }), _jsx("span", { "data-ui": "relation-caret", "aria-hidden": "true", children: "\u25BE" })] }), multiple && chosen().length ? (_jsx("ul", { "data-ui": "relation-chips", "aria-label": labels.chosen, children: each(chosen(), (value) => value, (value) => (_jsxs("li", { "data-ui": "relation-chip", children: [_jsx("span", { children: labelOf(value) }), _jsx("button", { "data-ui": "relation-chip-clear", type: "button", "aria-label": `${labels.clear}: ${labelOf(value)}`, title: labels.clear, disabled: disabled(), onClick: () => unchoose(value), children: "\u00D7" })] }))) })) : null, open() ? (_jsxs("div", { "data-ui": "relation-menu", id: `${islandId}-relation-menu`, children: [_jsx("input", { "data-ui": "relation-search", type: "search", value: query(), autocomplete: "off", placeholder: labels.search, "aria-label": labels.search, onInput: (event) => {
|
|
250
|
+
if (event.currentTarget instanceof HTMLInputElement)
|
|
251
|
+
query.set(event.currentTarget.value);
|
|
252
|
+
} }), _jsx("div", { "data-ui": "relation-options", role: "listbox", children: filteredOptions().length ? (each(filteredOptions(), (entry) => entry.value, (entry) => (_jsxs("button", { "data-ui": "relation-option", type: "button", role: "option", "data-selected": String(isChosen(entry.value)), "aria-selected": String(isChosen(entry.value)), onClick: () => choose(entry.value, entry.label, entry.description), children: [_jsx("span", { children: entry.label }), entry.description ? _jsx("small", { children: entry.description }) : null] })))) : (_jsx("p", { "data-ui": "relation-empty", children: labels.noRecords })) }), manager || !callbacks.options || callbacks.onCreate ? (_jsxs("footer", { "data-ui": "relation-footer", children: [manager || !callbacks.options ? (_jsx("button", { "data-ui": "action", "data-variant": "tertiary", type: "button", onClick: openDialog, children: labels.more })) : null, callbacks.onCreate ? (_jsx("button", { "data-ui": "action", "data-variant": "tertiary", type: "button", disabled: disabled(), onClick: () => {
|
|
253
|
+
if (disabled())
|
|
254
|
+
return;
|
|
255
|
+
const name = query().trim();
|
|
256
|
+
open.set(false);
|
|
257
|
+
query.set('');
|
|
258
|
+
callbacks.onCreate?.(name);
|
|
259
|
+
}, children: labels.create })) : null] })) : null] })) : null, dialog() ? dialogView() : null] })),
|
|
260
|
+
// The hidden native select stands in for a real one — including firing
|
|
261
|
+
// `change` for whatever the host page already listens for there (a state
|
|
262
|
+
// recomputed off another field's selection, say). Skip the effect's own
|
|
263
|
+
// eager first run: nothing has changed yet, only settled onto its initial value.
|
|
264
|
+
// The dispatch itself waits a microtask: firing it synchronously, still
|
|
265
|
+
// inside the signal write that is choosing this very value, let a listener
|
|
266
|
+
// that reacts by re-rendering (and so disposing and re-mounting this same
|
|
267
|
+
// island) re-enter this instance's own `choose()` while it was still on the
|
|
268
|
+
// stack — deferring runs it only once the choice has fully settled.
|
|
269
|
+
mount: ({ root, lifetime }) => {
|
|
270
|
+
let first = true;
|
|
271
|
+
const stop = effect(() => {
|
|
272
|
+
chosen();
|
|
273
|
+
if (first) {
|
|
274
|
+
first = false;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
queueMicrotask(() => {
|
|
278
|
+
if (disposed)
|
|
279
|
+
return;
|
|
280
|
+
const native = [...root.querySelectorAll('[data-ui="relation-native"]')][0];
|
|
281
|
+
if (native instanceof HTMLSelectElement)
|
|
282
|
+
native.dispatchEvent(new Event('change', { bubbles: true }));
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
lifetime.addEventListener('abort', stop);
|
|
286
|
+
// The inline dropdown isn't a <details> — nothing in the shared
|
|
287
|
+
// `attachDesignSystemInteractions` outside-click delegator sees it, so it
|
|
288
|
+
// closes itself the same way every other popup does. The dialog already
|
|
289
|
+
// closes on its own backdrop click.
|
|
290
|
+
document.addEventListener('click', (event) => {
|
|
291
|
+
if (!open())
|
|
292
|
+
return;
|
|
293
|
+
const target = event.target;
|
|
294
|
+
if (target instanceof Node && root.contains(target))
|
|
295
|
+
return;
|
|
296
|
+
open.set(false);
|
|
297
|
+
}, { signal: lifetime });
|
|
298
|
+
},
|
|
299
|
+
dispose: () => {
|
|
300
|
+
disposed = true;
|
|
301
|
+
clearTimeout(searchTimer);
|
|
302
|
+
activeRequest?.abort();
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export const relationSelect = (props) => createRelationSelectView(props);
|
|
307
|
+
//# sourceMappingURL=index.js.map
|