@ketvietlab/design-system 0.1.24 → 0.1.29

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.
Files changed (122) hide show
  1. package/README.md +29 -0
  2. package/dist/atlas/island-runtime.d.mts +2 -0
  3. package/dist/atlas/island-runtime.d.mts.map +1 -0
  4. package/dist/atlas/island-runtime.mjs +2 -0
  5. package/dist/atlas/island-runtime.mjs.map +1 -0
  6. package/dist/atlas/relation-select-hydration.d.ts +2 -0
  7. package/dist/atlas/relation-select-hydration.d.ts.map +1 -0
  8. package/dist/atlas/relation-select-hydration.js +40 -0
  9. package/dist/atlas/relation-select-hydration.js.map +1 -0
  10. package/dist/atlas-cli.d.ts.map +1 -1
  11. package/dist/atlas-cli.js +18 -3
  12. package/dist/atlas-cli.js.map +1 -1
  13. package/dist/catalogue/index.js +1 -1
  14. package/dist/catalogue/inventory-policy.json +49 -0
  15. package/dist/catalogue/inventory.generated.js +1 -1
  16. package/dist/catalogue/inventory.generated.js.map +1 -1
  17. package/dist/catalogue/inventory.generated.json +2613 -1092
  18. package/dist/catalogue/registry.d.ts.map +1 -1
  19. package/dist/catalogue/registry.js +18 -0
  20. package/dist/catalogue/registry.js.map +1 -1
  21. package/dist/catalogue/specimens.d.ts.map +1 -1
  22. package/dist/catalogue/specimens.js +130 -10
  23. package/dist/catalogue/specimens.js.map +1 -1
  24. package/dist/contract/index.d.ts.map +1 -1
  25. package/dist/contract/index.js +10 -0
  26. package/dist/contract/index.js.map +1 -1
  27. package/dist/foundations/tokens.css +34 -26
  28. package/dist/index.d.ts +14 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/interactions/ket-table/demo.d.ts +4 -0
  33. package/dist/interactions/ket-table/demo.d.ts.map +1 -0
  34. package/dist/interactions/ket-table/demo.js +147 -0
  35. package/dist/interactions/ket-table/demo.js.map +1 -0
  36. package/dist/interactions/ket-table/index.d.ts +199 -0
  37. package/dist/interactions/ket-table/index.d.ts.map +1 -0
  38. package/dist/interactions/ket-table/index.js +376 -0
  39. package/dist/interactions/ket-table/index.js.map +1 -0
  40. package/dist/interactions/ket-table/styles.css +282 -0
  41. package/dist/interactions/lightbox/demo.d.ts +3 -0
  42. package/dist/interactions/lightbox/demo.d.ts.map +1 -0
  43. package/dist/interactions/lightbox/demo.js +21 -0
  44. package/dist/interactions/lightbox/demo.js.map +1 -0
  45. package/dist/interactions/lightbox/index.d.ts +58 -0
  46. package/dist/interactions/lightbox/index.d.ts.map +1 -0
  47. package/dist/interactions/lightbox/index.js +220 -0
  48. package/dist/interactions/lightbox/index.js.map +1 -0
  49. package/dist/interactions/lightbox/styles.css +153 -0
  50. package/dist/interactions/relation-select/demo.d.ts +3 -0
  51. package/dist/interactions/relation-select/demo.d.ts.map +1 -0
  52. package/dist/interactions/relation-select/demo.js +36 -0
  53. package/dist/interactions/relation-select/demo.js.map +1 -0
  54. package/dist/interactions/relation-select/index.d.ts +87 -0
  55. package/dist/interactions/relation-select/index.d.ts.map +1 -0
  56. package/dist/interactions/relation-select/index.js +307 -0
  57. package/dist/interactions/relation-select/index.js.map +1 -0
  58. package/dist/interactions/relation-select/styles.css +311 -0
  59. package/dist/interactions/reorder-list/index.d.ts +23 -0
  60. package/dist/interactions/reorder-list/index.d.ts.map +1 -0
  61. package/dist/interactions/reorder-list/index.js +15 -0
  62. package/dist/interactions/reorder-list/index.js.map +1 -0
  63. package/dist/interactions/reorder-list/runtime.d.ts +5 -0
  64. package/dist/interactions/reorder-list/runtime.d.ts.map +1 -0
  65. package/dist/interactions/reorder-list/runtime.js +85 -0
  66. package/dist/interactions/reorder-list/runtime.js.map +1 -0
  67. package/dist/interactions/reorder-list/styles.css +33 -0
  68. package/dist/interactions/search-filter/demo.d.ts +3 -0
  69. package/dist/interactions/search-filter/demo.d.ts.map +1 -0
  70. package/dist/interactions/search-filter/demo.js +74 -0
  71. package/dist/interactions/search-filter/demo.js.map +1 -0
  72. package/dist/interactions/search-filter/index.d.ts +153 -0
  73. package/dist/interactions/search-filter/index.d.ts.map +1 -0
  74. package/dist/interactions/search-filter/index.js +824 -0
  75. package/dist/interactions/search-filter/index.js.map +1 -0
  76. package/dist/interactions/search-filter/styles.css +580 -0
  77. package/dist/layouts/app-navigation/index.d.ts +11 -5
  78. package/dist/layouts/app-navigation/index.d.ts.map +1 -1
  79. package/dist/layouts/app-navigation/index.js +5 -3
  80. package/dist/layouts/app-navigation/index.js.map +1 -1
  81. package/dist/layouts/app-navigation/styles.css +50 -0
  82. package/dist/layouts/page-identity/index.d.ts +2 -0
  83. package/dist/layouts/page-identity/index.d.ts.map +1 -1
  84. package/dist/layouts/page-identity/index.js +2 -2
  85. package/dist/layouts/page-identity/index.js.map +1 -1
  86. package/dist/layouts/shell/index.d.ts +34 -1
  87. package/dist/layouts/shell/index.d.ts.map +1 -1
  88. package/dist/layouts/shell/index.js +26 -2
  89. package/dist/layouts/shell/index.js.map +1 -1
  90. package/dist/layouts/shell/styles.css +283 -0
  91. package/dist/patterns/data-table/styles.css +1 -1
  92. package/dist/patterns/list-page/index.d.ts +10 -3
  93. package/dist/patterns/list-page/index.d.ts.map +1 -1
  94. package/dist/patterns/list-page/index.js +10 -6
  95. package/dist/patterns/list-page/index.js.map +1 -1
  96. package/dist/patterns/list-page/responsive.css +1 -5
  97. package/dist/patterns/list-page/styles.css +25 -3
  98. package/dist/patterns/modal-sheet/index.d.ts +2 -0
  99. package/dist/patterns/modal-sheet/index.d.ts.map +1 -1
  100. package/dist/patterns/modal-sheet/index.js +3 -1
  101. package/dist/patterns/modal-sheet/index.js.map +1 -1
  102. package/dist/patterns/record-form/styles.css +34 -0
  103. package/dist/primitives/actions/index.d.ts +4 -0
  104. package/dist/primitives/actions/index.d.ts.map +1 -1
  105. package/dist/primitives/actions/index.js +2 -2
  106. package/dist/primitives/actions/index.js.map +1 -1
  107. package/dist/primitives/actions/styles.css +9 -0
  108. package/dist/primitives/feedback/styles.css +4 -1
  109. package/dist/primitives/status/index.d.ts +12 -1
  110. package/dist/primitives/status/index.d.ts.map +1 -1
  111. package/dist/primitives/status/index.js +16 -1
  112. package/dist/primitives/status/index.js.map +1 -1
  113. package/dist/primitives/status/styles.css +28 -0
  114. package/dist/record/formatted-values/index.d.ts +2 -1
  115. package/dist/record/formatted-values/index.d.ts.map +1 -1
  116. package/dist/record/formatted-values/index.js +2 -1
  117. package/dist/record/formatted-values/index.js.map +1 -1
  118. package/dist/runtime/index.d.ts.map +1 -1
  119. package/dist/runtime/index.js +129 -2
  120. package/dist/runtime/index.js.map +1 -1
  121. package/dist/styles.css +1 -1
  122. package/package.json +2 -2
@@ -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,3 @@
1
+ import type { RelationSelectConfig } from './index.tsx';
2
+ export declare const relationSelectDemoConfig: RelationSelectConfig;
3
+ //# sourceMappingURL=demo.d.ts.map
@@ -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":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAkB,MAAM,wBAAwB,CAAA;AAE3F,eAAO,MAAM,KAAK,YAChB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,CACjB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAE1F,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,oBAAoB,CAAA;IAC5B,OAAO,CAAC,EAAE,eAAe,CAAA;CAC1B,CAAA;AAED,oFAAoF;AACpF,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,cAAc,EAAE,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAID,KAAK,yBAAyB,GAAG,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAA;AA6B3F,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,yBAAyB,EAChC,SAAS,GAAE,uBAA4B,GACtC,gBAAgB,CAskBlB;AAED,eAAO,MAAM,cAAc,UAAW,WAAW,KAAG,gBACU,CAAA"}
@@ -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