@sekiui/elements 0.0.88 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cdn/p-CQQRfw1L.js +18 -0
  2. package/dist/cdn/seki-date-picker.js +71 -36
  3. package/dist/cdn/seki-drawer.js +76 -32
  4. package/dist/cdn/seki-hover-card.js +58 -3
  5. package/dist/cdn/seki-select-content.js +21 -13
  6. package/dist/cdn/seki-select-group.js +3 -4
  7. package/dist/cdn/seki-select-option.js +33 -15
  8. package/dist/cdn/seki-select-trigger.js +6 -4
  9. package/dist/cdn/seki-sheet.js +71 -30
  10. package/dist/cdn/seki-tooltip.js +47 -2
  11. package/dist/cjs/loader.cjs.js +1 -1
  12. package/dist/cjs/option-id-wIa73qWK.js +20 -0
  13. package/dist/cjs/seki-date-picker.cjs.entry.js +64 -32
  14. package/dist/cjs/seki-drawer.cjs.entry.js +67 -27
  15. package/dist/cjs/seki-hover-card.cjs.entry.js +52 -2
  16. package/dist/cjs/seki-select-content.cjs.entry.js +20 -11
  17. package/dist/cjs/seki-select-group.cjs.entry.js +2 -2
  18. package/dist/cjs/seki-select-option.cjs.entry.js +32 -13
  19. package/dist/cjs/seki-select-trigger.cjs.entry.js +5 -2
  20. package/dist/cjs/seki-sheet.cjs.entry.js +62 -25
  21. package/dist/cjs/seki-tooltip.cjs.entry.js +43 -2
  22. package/dist/cjs/sekiui.cjs.js +1 -1
  23. package/dist/collection/components/date-picker/seki-date-picker.js +124 -36
  24. package/dist/collection/components/drawer/seki-drawer.js +132 -31
  25. package/dist/collection/components/hover-card/seki-hover-card.js +138 -2
  26. package/dist/collection/components/select-content/seki-select-content.css +8 -0
  27. package/dist/collection/components/select-content/seki-select-content.js +20 -11
  28. package/dist/collection/components/select-group/seki-select-group.js +2 -2
  29. package/dist/collection/components/select-option/seki-select-option.js +32 -13
  30. package/dist/collection/components/select-trigger/seki-select-trigger.js +5 -2
  31. package/dist/collection/components/sheet/seki-sheet.js +127 -29
  32. package/dist/collection/components/tooltip/seki-tooltip.js +92 -2
  33. package/dist/collection/utils/select/index.js +1 -0
  34. package/dist/collection/utils/select/option-id.js +16 -0
  35. package/dist/components/p-CQQRfw1L.js +18 -0
  36. package/dist/components/seki-date-picker.js +71 -36
  37. package/dist/components/seki-drawer.js +76 -32
  38. package/dist/components/seki-hover-card.js +58 -3
  39. package/dist/components/seki-select-content.js +21 -13
  40. package/dist/components/seki-select-group.js +3 -4
  41. package/dist/components/seki-select-option.js +33 -15
  42. package/dist/components/seki-select-trigger.js +6 -4
  43. package/dist/components/seki-sheet.js +71 -30
  44. package/dist/components/seki-tooltip.js +47 -2
  45. package/dist/esm/loader.js +1 -1
  46. package/dist/esm/option-id-CQQRfw1L.js +18 -0
  47. package/dist/esm/seki-date-picker.entry.js +64 -32
  48. package/dist/esm/seki-drawer.entry.js +67 -27
  49. package/dist/esm/seki-hover-card.entry.js +52 -2
  50. package/dist/esm/seki-select-content.entry.js +20 -11
  51. package/dist/esm/seki-select-group.entry.js +2 -2
  52. package/dist/esm/seki-select-option.entry.js +32 -13
  53. package/dist/esm/seki-select-trigger.entry.js +5 -2
  54. package/dist/esm/seki-sheet.entry.js +62 -25
  55. package/dist/esm/seki-tooltip.entry.js +43 -2
  56. package/dist/esm/sekiui.js +1 -1
  57. package/dist/sekiui/p-05b33a61.entry.js +1 -0
  58. package/dist/sekiui/p-1cd6b3e6.entry.js +1 -0
  59. package/dist/sekiui/p-3de5878b.entry.js +1 -0
  60. package/dist/sekiui/p-649dbdf0.entry.js +1 -0
  61. package/dist/sekiui/p-CQQRfw1L.js +1 -0
  62. package/dist/sekiui/p-a2ae6d1b.entry.js +1 -0
  63. package/dist/sekiui/p-b82c2267.entry.js +1 -0
  64. package/dist/sekiui/p-c0f21b6a.entry.js +1 -0
  65. package/dist/sekiui/p-c3ef507f.entry.js +1 -0
  66. package/dist/sekiui/p-fe3e661d.entry.js +1 -0
  67. package/dist/sekiui/sekiui.esm.js +1 -1
  68. package/dist/types/components/date-picker/seki-date-picker.d.ts +21 -5
  69. package/dist/types/components/drawer/seki-drawer.d.ts +22 -6
  70. package/dist/types/components/hover-card/seki-hover-card.d.ts +21 -0
  71. package/dist/types/components/select-content/seki-select-content.d.ts +8 -5
  72. package/dist/types/components/select-option/seki-select-option.d.ts +6 -4
  73. package/dist/types/components/sheet/seki-sheet.d.ts +22 -6
  74. package/dist/types/components/tooltip/seki-tooltip.d.ts +13 -0
  75. package/dist/types/components.d.ts +132 -9
  76. package/dist/types/utils/select/index.d.ts +1 -0
  77. package/dist/types/utils/select/option-id.d.ts +14 -0
  78. package/package.json +1 -1
  79. package/dist/sekiui/p-1866201e.entry.js +0 -1
  80. package/dist/sekiui/p-2e18933c.entry.js +0 -1
  81. package/dist/sekiui/p-779764c0.entry.js +0 -1
  82. package/dist/sekiui/p-786c153b.entry.js +0 -1
  83. package/dist/sekiui/p-87bcbb66.entry.js +0 -1
  84. package/dist/sekiui/p-9abfa3a5.entry.js +0 -1
  85. package/dist/sekiui/p-ac7bd611.entry.js +0 -1
  86. package/dist/sekiui/p-c62c4ad7.entry.js +0 -1
  87. package/dist/sekiui/p-e45e6992.entry.js +0 -1
@@ -19,51 +19,75 @@ import { syncTheme } from "../../utils/theme-sync";
19
19
  */
20
20
  export class SekiDrawer {
21
21
  constructor() {
22
+ /**
23
+ * Controlled open state. Also readable/writable as the `open` attribute, so
24
+ * `<seki-drawer open>` renders open with no script involved.
25
+ */
26
+ this.open = false;
27
+ /**
28
+ * Initial open state for uncontrolled usage
29
+ */
30
+ this.defaultOpen = false;
22
31
  /**
23
32
  * Internal state for drawer open/closed
24
33
  */
25
- this.isOpen = false;
34
+ this._isOpen = false;
26
35
  /**
27
36
  * Current snap point (0-1)
28
37
  */
29
38
  this.currentSnapPoint = 1;
30
39
  }
40
+ componentWillLoad() {
41
+ this._isOpen = this.open || this.defaultOpen;
42
+ this.open = this._isOpen;
43
+ this.currentSnapPoint = this._isOpen ? 1 : 0;
44
+ }
45
+ componentDidLoad() {
46
+ this.updateContext();
47
+ }
48
+ handleOpenPropChange(newValue) {
49
+ // An outside assignment (`drawer.open = true`) has to win after first render;
50
+ // `open` is only read once in componentWillLoad otherwise.
51
+ if (newValue === this._isOpen)
52
+ return;
53
+ if (newValue) {
54
+ this.openDrawer();
55
+ }
56
+ else {
57
+ this.closeDrawer();
58
+ }
59
+ }
31
60
  /**
32
61
  * Opens the drawer
33
62
  */
34
- async open() {
35
- this.isOpen = true;
63
+ async openDrawer() {
64
+ this._isOpen = true;
65
+ this.open = true;
36
66
  this.currentSnapPoint = 1;
37
67
  this.updateContext();
38
68
  this.drawerOpen.emit();
69
+ this.sekiOpenChange.emit(true);
39
70
  }
40
71
  /**
41
72
  * Closes the drawer
42
73
  */
43
- async close() {
44
- this.isOpen = false;
74
+ async closeDrawer() {
75
+ this._isOpen = false;
76
+ this.open = false;
45
77
  this.currentSnapPoint = 0;
46
78
  this.updateContext();
47
79
  this.drawerClose.emit();
48
- }
49
- updateContext() {
50
- // seki-drawer-content has no other way to know this drawer's open state —
51
- // it reads this off the host element via closest('seki-drawer').
52
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
- this.el._drawerContext = { isOpen: this.isOpen };
54
- }
55
- componentDidLoad() {
56
- this.updateContext();
80
+ this.sekiOpenChange.emit(false);
57
81
  }
58
82
  /**
59
83
  * Toggles the drawer
60
84
  */
61
- async toggle() {
62
- if (this.isOpen) {
63
- await this.close();
85
+ async toggleDrawer() {
86
+ if (this._isOpen) {
87
+ await this.closeDrawer();
64
88
  }
65
89
  else {
66
- await this.open();
90
+ await this.openDrawer();
67
91
  }
68
92
  }
69
93
  /**
@@ -71,37 +95,49 @@ export class SekiDrawer {
71
95
  */
72
96
  async snapTo(point) {
73
97
  if (point <= 0) {
74
- await this.close();
98
+ await this.closeDrawer();
75
99
  }
76
100
  else {
77
101
  this.currentSnapPoint = point;
78
- this.isOpen = true;
102
+ const wasOpen = this._isOpen;
103
+ this._isOpen = true;
104
+ this.open = true;
79
105
  this.updateContext();
106
+ if (!wasOpen) {
107
+ this.drawerOpen.emit();
108
+ this.sekiOpenChange.emit(true);
109
+ }
80
110
  this.drawerSnap.emit({ point });
81
111
  }
82
112
  }
113
+ updateContext() {
114
+ // seki-drawer-content has no other way to know this drawer's open state —
115
+ // it reads this off the host element via closest('seki-drawer').
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ this.el._drawerContext = { isOpen: this._isOpen };
118
+ }
83
119
  handleTriggerClick() {
84
- this.toggle();
120
+ this.toggleDrawer();
85
121
  }
86
122
  handleRequestClose() {
87
- this.close();
123
+ this.closeDrawer();
88
124
  }
89
125
  handleRequestSnap(event) {
90
126
  this.snapTo(event.detail.point);
91
127
  }
92
128
  handleKeydown(ev) {
93
- if (this.isOpen && ev.key === 'Escape') {
94
- this.close();
129
+ if (this._isOpen && ev.key === 'Escape') {
130
+ this.closeDrawer();
95
131
  }
96
132
  }
97
133
  connectedCallback() {
98
134
  syncTheme(this.el);
99
135
  }
100
136
  render() {
101
- return (h("div", { key: '7ae3c36bf05c4e2d9957083912da105b559a47e6', class: {
137
+ return (h("div", { key: '824d218c42cd41a0a79bef604e940f6c95a64470', class: {
102
138
  'seki-drawer': true,
103
- 'seki-drawer--open': this.isOpen,
104
- }, "data-snap-point": this.currentSnapPoint, part: "drawer" }, h("slot", { key: '6ef97e42496716b572d85d460dc25b63fdb8ff56' }), this.isOpen && (h("div", { key: 'fd17a0aa91fbd193a47ac1449eb85e6b972fe586', class: "seki-drawer-overlay", part: "overlay", onClick: () => this.close() }))));
139
+ 'seki-drawer--open': this._isOpen,
140
+ }, "data-snap-point": this.currentSnapPoint, part: "drawer" }, h("slot", { key: '26982c14006c01915390898306d79fa665e30463' }), this._isOpen && (h("div", { key: 'a0f90520c0deaa5c320bbc027ca5b6a4edc4d9ff', class: "seki-drawer-overlay", part: "overlay", onClick: () => this.closeDrawer() }))));
105
141
  }
106
142
  static get is() { return "seki-drawer"; }
107
143
  static get encapsulation() { return "shadow"; }
@@ -115,9 +151,53 @@ export class SekiDrawer {
115
151
  "$": ["seki-drawer.css"]
116
152
  };
117
153
  }
154
+ static get properties() {
155
+ return {
156
+ "open": {
157
+ "type": "boolean",
158
+ "mutable": true,
159
+ "complexType": {
160
+ "original": "boolean",
161
+ "resolved": "boolean",
162
+ "references": {}
163
+ },
164
+ "required": false,
165
+ "optional": false,
166
+ "docs": {
167
+ "tags": [],
168
+ "text": "Controlled open state. Also readable/writable as the `open` attribute, so\n`<seki-drawer open>` renders open with no script involved."
169
+ },
170
+ "getter": false,
171
+ "setter": false,
172
+ "reflect": true,
173
+ "attribute": "open",
174
+ "defaultValue": "false"
175
+ },
176
+ "defaultOpen": {
177
+ "type": "boolean",
178
+ "mutable": false,
179
+ "complexType": {
180
+ "original": "boolean",
181
+ "resolved": "boolean",
182
+ "references": {}
183
+ },
184
+ "required": false,
185
+ "optional": false,
186
+ "docs": {
187
+ "tags": [],
188
+ "text": "Initial open state for uncontrolled usage"
189
+ },
190
+ "getter": false,
191
+ "setter": false,
192
+ "reflect": false,
193
+ "attribute": "default-open",
194
+ "defaultValue": "false"
195
+ }
196
+ };
197
+ }
118
198
  static get states() {
119
199
  return {
120
- "isOpen": {},
200
+ "_isOpen": {},
121
201
  "currentSnapPoint": {}
122
202
  };
123
203
  }
@@ -167,11 +247,26 @@ export class SekiDrawer {
167
247
  "resolved": "{ point: number; }",
168
248
  "references": {}
169
249
  }
250
+ }, {
251
+ "method": "sekiOpenChange",
252
+ "name": "sekiOpenChange",
253
+ "bubbles": true,
254
+ "cancelable": true,
255
+ "composed": true,
256
+ "docs": {
257
+ "tags": [],
258
+ "text": "Emitted whenever the open state changes. Matches the dialog/popover/collapsible\nevent shape so a consumer can listen to one event name across every overlay."
259
+ },
260
+ "complexType": {
261
+ "original": "boolean",
262
+ "resolved": "boolean",
263
+ "references": {}
264
+ }
170
265
  }];
171
266
  }
172
267
  static get methods() {
173
268
  return {
174
- "open": {
269
+ "openDrawer": {
175
270
  "complexType": {
176
271
  "signature": "() => Promise<void>",
177
272
  "parameters": [],
@@ -188,7 +283,7 @@ export class SekiDrawer {
188
283
  "tags": []
189
284
  }
190
285
  },
191
- "close": {
286
+ "closeDrawer": {
192
287
  "complexType": {
193
288
  "signature": "() => Promise<void>",
194
289
  "parameters": [],
@@ -205,7 +300,7 @@ export class SekiDrawer {
205
300
  "tags": []
206
301
  }
207
302
  },
208
- "toggle": {
303
+ "toggleDrawer": {
209
304
  "complexType": {
210
305
  "signature": "() => Promise<void>",
211
306
  "parameters": [],
@@ -246,6 +341,12 @@ export class SekiDrawer {
246
341
  };
247
342
  }
248
343
  static get elementRef() { return "el"; }
344
+ static get watchers() {
345
+ return [{
346
+ "propName": "open",
347
+ "methodName": "handleOpenPropChange"
348
+ }];
349
+ }
249
350
  static get listeners() {
250
351
  return [{
251
352
  "name": "triggerClick",
@@ -54,6 +54,17 @@ export class SekiHoverCard {
54
54
  * @default false
55
55
  */
56
56
  this.disabled = false;
57
+ /**
58
+ * Controlled open state. Reflects to the `open` attribute, so
59
+ * `<seki-hover-card open>` renders open with no script involved.
60
+ * @default false
61
+ */
62
+ this.open = false;
63
+ /**
64
+ * Initial open state for uncontrolled usage
65
+ * @default false
66
+ */
67
+ this.defaultOpen = false;
57
68
  /**
58
69
  * Internal open state
59
70
  */
@@ -114,6 +125,20 @@ export class SekiHoverCard {
114
125
  }
115
126
  };
116
127
  }
128
+ componentWillLoad() {
129
+ // `disabled` wins over a requested initial open state.
130
+ this._isOpen = (this.open || this.defaultOpen) && !this.disabled;
131
+ this.open = this._isOpen;
132
+ }
133
+ openChanged(newValue) {
134
+ // An outside assignment (`card.open = true`) has to win after first render;
135
+ // `open` is only read once in componentWillLoad otherwise.
136
+ if (newValue === this._isOpen)
137
+ return;
138
+ this.cancelOpen();
139
+ this.cancelClose();
140
+ this.setOpen(newValue);
141
+ }
117
142
  disabledChanged(newValue) {
118
143
  if (newValue && this._isOpen) {
119
144
  this.setOpen(false);
@@ -123,9 +148,17 @@ export class SekiHoverCard {
123
148
  * Set the open state and emit change event
124
149
  */
125
150
  setOpen(open) {
126
- if (this._isOpen === open)
151
+ if (open && this.disabled) {
152
+ // Keep the reflected prop honest when an open request is refused.
153
+ this.open = false;
127
154
  return;
155
+ }
156
+ if (this._isOpen === open) {
157
+ this.open = open;
158
+ return;
159
+ }
128
160
  this._isOpen = open;
161
+ this.open = open;
129
162
  this.updateContext();
130
163
  this.sekiOpenChange.emit({ open });
131
164
  if (open) {
@@ -135,6 +168,22 @@ export class SekiHoverCard {
135
168
  this.removeScrollListener();
136
169
  }
137
170
  }
171
+ /**
172
+ * Open the hover card immediately, skipping `openDelay`
173
+ */
174
+ async openHoverCard() {
175
+ this.cancelOpen();
176
+ this.cancelClose();
177
+ this.setOpen(true);
178
+ }
179
+ /**
180
+ * Close the hover card immediately, skipping `closeDelay`
181
+ */
182
+ async closeHoverCard() {
183
+ this.cancelOpen();
184
+ this.cancelClose();
185
+ this.setOpen(false);
186
+ }
138
187
  /**
139
188
  * Close the hover card immediately and focus the trigger
140
189
  */
@@ -233,7 +282,7 @@ export class SekiHoverCard {
233
282
  this.removeScrollListener();
234
283
  }
235
284
  render() {
236
- return (h(Host, { key: '228ce747491865bdc97fdef9b6da751f5833c588', "data-state": this._isOpen ? 'open' : 'closed' }, h("div", { key: '777dade24e948b0e8ed21ebfa9595beb33fdf7a9', class: "hover-card" }, h("slot", { key: 'e5db3d1887de0241a95a7529aa3c5cb8d9c79ed2' }))));
285
+ return (h(Host, { key: '2a5bed9d9abe8647cdbd202d97984a05c8bb9eb1', "data-state": this._isOpen ? 'open' : 'closed' }, h("div", { key: 'c3f1e0be795cea7a59c08d2a341c7a40d8754e04', class: "hover-card" }, h("slot", { key: '0021d9d0ede67170a903db785c6b334ffc5aaba7' }))));
237
286
  }
238
287
  static get is() { return "seki-hover-card"; }
239
288
  static get encapsulation() { return "shadow"; }
@@ -375,6 +424,52 @@ export class SekiHoverCard {
375
424
  "reflect": false,
376
425
  "attribute": "disabled",
377
426
  "defaultValue": "false"
427
+ },
428
+ "open": {
429
+ "type": "boolean",
430
+ "mutable": true,
431
+ "complexType": {
432
+ "original": "boolean",
433
+ "resolved": "boolean",
434
+ "references": {}
435
+ },
436
+ "required": false,
437
+ "optional": false,
438
+ "docs": {
439
+ "tags": [{
440
+ "name": "default",
441
+ "text": "false"
442
+ }],
443
+ "text": "Controlled open state. Reflects to the `open` attribute, so\n`<seki-hover-card open>` renders open with no script involved."
444
+ },
445
+ "getter": false,
446
+ "setter": false,
447
+ "reflect": true,
448
+ "attribute": "open",
449
+ "defaultValue": "false"
450
+ },
451
+ "defaultOpen": {
452
+ "type": "boolean",
453
+ "mutable": false,
454
+ "complexType": {
455
+ "original": "boolean",
456
+ "resolved": "boolean",
457
+ "references": {}
458
+ },
459
+ "required": false,
460
+ "optional": false,
461
+ "docs": {
462
+ "tags": [{
463
+ "name": "default",
464
+ "text": "false"
465
+ }],
466
+ "text": "Initial open state for uncontrolled usage"
467
+ },
468
+ "getter": false,
469
+ "setter": false,
470
+ "reflect": false,
471
+ "attribute": "default-open",
472
+ "defaultValue": "false"
378
473
  }
379
474
  };
380
475
  }
@@ -401,9 +496,50 @@ export class SekiHoverCard {
401
496
  }
402
497
  }];
403
498
  }
499
+ static get methods() {
500
+ return {
501
+ "openHoverCard": {
502
+ "complexType": {
503
+ "signature": "() => Promise<void>",
504
+ "parameters": [],
505
+ "references": {
506
+ "Promise": {
507
+ "location": "global",
508
+ "id": "global::Promise"
509
+ }
510
+ },
511
+ "return": "Promise<void>"
512
+ },
513
+ "docs": {
514
+ "text": "Open the hover card immediately, skipping `openDelay`",
515
+ "tags": []
516
+ }
517
+ },
518
+ "closeHoverCard": {
519
+ "complexType": {
520
+ "signature": "() => Promise<void>",
521
+ "parameters": [],
522
+ "references": {
523
+ "Promise": {
524
+ "location": "global",
525
+ "id": "global::Promise"
526
+ }
527
+ },
528
+ "return": "Promise<void>"
529
+ },
530
+ "docs": {
531
+ "text": "Close the hover card immediately, skipping `closeDelay`",
532
+ "tags": []
533
+ }
534
+ }
535
+ };
536
+ }
404
537
  static get elementRef() { return "el"; }
405
538
  static get watchers() {
406
539
  return [{
540
+ "propName": "open",
541
+ "methodName": "openChanged"
542
+ }, {
407
543
  "propName": "disabled",
408
544
  "methodName": "disabledChanged"
409
545
  }];
@@ -816,6 +816,14 @@
816
816
  min-width: 100%;
817
817
  }
818
818
 
819
+ /* Hides the listbox (and, since it's no longer un-slotted, every option
820
+ inside it) while closed. See the comment on render()'s Host class in
821
+ seki-select-content.tsx for why this replaced a conditional `return null`.
822
+ Mirrors seki-combobox-content.css's identical :host(.hidden) rule. */
823
+ :host(.hidden) {
824
+ display: none;
825
+ }
826
+
819
827
  .content-container {
820
828
  background: var(--seki-select-content-bg);
821
829
  border: var(--seki-select-content-border);
@@ -52,11 +52,14 @@ export class SekiSelectContent {
52
52
  /**
53
53
  * Accessible name for the listbox.
54
54
  *
55
- * It cannot borrow the trigger's name via aria-labelledby: the trigger's
56
- * combobox and this listbox live in separate shadow roots, and IDREFs do not
57
- * resolve across tree scopes. So the name is mirrored down as a literal
58
- * string instead. An aria-labelledby on the <seki-select> host *can* be
59
- * resolved here, because the host and its label sit in the same tree.
55
+ * As of #300, seki-select-trigger and this listbox render in the same real
56
+ * DOM tree scope (scoped, non-shadow mode), so an aria-labelledby pointing
57
+ * back at the trigger's combobox would technically resolve now. Kept as a
58
+ * mirrored literal string anyway — simpler and more robust than a live
59
+ * cross-component id reference, and it avoids coupling this component's
60
+ * accessible name to the trigger's specific DOM shape. An aria-labelledby
61
+ * on the <seki-select> host *can* still be resolved here, because the host
62
+ * and its label sit in the same tree (unchanged).
60
63
  */
61
64
  getAccessibleName(selectContext) {
62
65
  var _a, _b, _c;
@@ -81,16 +84,22 @@ export class SekiSelectContent {
81
84
  return null;
82
85
  }
83
86
  const isOpen = selectContext.open;
84
- // Lazy rendering - only render when open
85
- if (!isOpen) {
86
- return null;
87
- }
88
87
  const parent = this.el.closest('seki-select');
89
88
  const maxHeight = (parent === null || parent === void 0 ? void 0 : parent.maxHeight) || '12.5rem';
90
- return (h(Host, null, h("div", { class: "content-container", role: "listbox", id: selectContext.contentId, "aria-label": this.getAccessibleName(selectContext), "aria-multiselectable": selectContext.multiple ? 'true' : undefined }, h("div", { ref: (el) => (this.scrollContainerRef = el), class: "content-scroll", style: { maxHeight } }, h("slot", null)))));
89
+ // Always render, hiding via CSS instead of `return null` when closed (the
90
+ // seki-combobox-content pattern this mirrors). Under shadow DOM this used
91
+ // to conditionally return null and rely on the browser's own behavior of
92
+ // never rendering un-slotted light-DOM children — with no <slot> to
93
+ // assign them to, the options were simply invisible. Scoped (non-shadow)
94
+ // mode has no such mechanism: each seki-select-option still renders its
95
+ // own role="option" div regardless of whether a parent slot exists for
96
+ // it, so returning null here left every option fully visible — and
97
+ // interactable — even while "closed" (#300 e2e regression). See
98
+ // :host(.hidden) in seki-select-content.css.
99
+ return (h(Host, { class: { hidden: !isOpen } }, h("div", { class: "content-container", role: "listbox", id: selectContext.contentId, "aria-label": this.getAccessibleName(selectContext), "aria-multiselectable": selectContext.multiple ? 'true' : undefined }, h("div", { ref: (el) => (this.scrollContainerRef = el), class: "content-scroll", style: { maxHeight } }, h("slot", null)))));
91
100
  }
92
101
  static get is() { return "seki-select-content"; }
93
- static get encapsulation() { return "shadow"; }
102
+ static get encapsulation() { return "scoped"; }
94
103
  static get originalStyleUrls() {
95
104
  return {
96
105
  "$": ["seki-select-content.css"]
@@ -15,10 +15,10 @@ export class SekiSelectGroup {
15
15
  syncTheme(this.el);
16
16
  }
17
17
  render() {
18
- return (h(Host, { key: 'e221fca22d8e6fdf3addd112fb08afe46cf888ce' }, h("div", { key: '365fe2f5ed8990ebb80d41904a2cc79534567f9f', class: "group", role: "group", "aria-labelledby": this.groupLabelId }, h("div", { key: 'd2c7e82bf5cb0c3cd108d8428e44c8d1654f77a4', class: "group-separator", "aria-hidden": "true" }), h("div", { key: '0a6737c20344303ce09bab49d0c1fe913f29a216', class: "group-label", id: this.groupLabelId, role: "presentation" }, h("slot", { key: 'a492f1b53627832fdb1b801b4115f2d4954f30f0', name: "label" }, this.label)), h("div", { key: 'eef01caf57f171ab6ce6ac66840c1cb4fc995a79', class: "group-options" }, h("slot", { key: 'fdb252d3a0e2e75e863104a66469cff17730e0ce' })))));
18
+ return (h(Host, { key: 'e1cb8d09575a3851c93f823623cd9331be3e96cc' }, h("div", { key: 'ddb385f0668bb61461f9e5121be9b8394218b629', class: "group", role: "group", "aria-labelledby": this.groupLabelId }, h("div", { key: 'de153ac27303e514ab3f6c85547809523b41584a', class: "group-separator", "aria-hidden": "true" }), h("div", { key: 'f8e67ce663167d38ff6432b704abec5631c5d52c', class: "group-label", id: this.groupLabelId, role: "presentation" }, h("slot", { key: '622315b7efb3aafc12b93f0b0bbf32a22292c95b', name: "label" }, this.label)), h("div", { key: 'beb13a0aeac5543fb9dcde5da98caf81d92a23e2', class: "group-options" }, h("slot", { key: '8919337fddfeb602538eaca3914a1e4bb36c65ee' })))));
19
19
  }
20
20
  static get is() { return "seki-select-group"; }
21
- static get encapsulation() { return "shadow"; }
21
+ static get encapsulation() { return "scoped"; }
22
22
  static get originalStyleUrls() {
23
23
  return {
24
24
  "$": ["seki-select-group.css"]
@@ -1,4 +1,5 @@
1
1
  import { Host, h } from "@stencil/core";
2
+ import { buildOptionId } from "../../utils/select";
2
3
  import { syncTheme } from "../../utils/theme-sync";
3
4
  import { subscribeToContextChange } from "../../utils/context-bridge";
4
5
  /**
@@ -15,19 +16,20 @@ export class SekiSelectOption {
15
16
  this.hasRegistered = false;
16
17
  this.contextSubscription = { parent: null, unsubscribe: () => { } };
17
18
  /**
18
- * Refresh aria-selected/data-selected/data-focused directly on the shadow
19
- * DOM whenever the parent's value/focus state changes post-mount. This is
20
- * applied imperatively rather than through a Stencil @State bump + render()
19
+ * Refresh aria-selected/data-selected/data-focused directly on the
20
+ * rendered DOM whenever the parent's value/focus state changes post-mount.
21
+ * This is applied imperatively rather than through a Stencil @State bump +
22
+ * render()
21
23
  * — a component that reads a parent's state via closest() + an
22
24
  * addEventListener callback and relies on render() re-invoking to reflect
23
25
  * it is a confirmed-unreliable shape in this codebase (#244). Mutating the
24
- * shadow DOM directly here side-steps that entirely.
26
+ * DOM directly here side-steps that entirely.
25
27
  */
26
28
  this.handleParentContextChange = () => {
27
- var _a;
28
- const optionEl = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.option');
29
+ const optionEl = this.el.querySelector('.option');
29
30
  if (!optionEl)
30
31
  return;
32
+ optionEl.id = this.optionId;
31
33
  const selectContext = this.getSelectContext();
32
34
  const isSelected = (selectContext === null || selectContext === void 0 ? void 0 : selectContext.isValueSelected(this.value)) || false;
33
35
  const isFocused = (selectContext === null || selectContext === void 0 ? void 0 : selectContext.focusedOptionValue) === this.value;
@@ -75,15 +77,21 @@ export class SekiSelectOption {
75
77
  // ===========================
76
78
  // State
77
79
  // ===========================
78
- // Deterministic id, scoped to this option's own shadow root. seki-select-trigger
79
- // no longer references it via aria-activedescendant (see #292 — the id lives in
80
- // a different tree scope than the trigger's button, so that IDREF could never
81
- // resolve); kept for scrollToOption() and as a stable hook for tests/consumers.
80
+ // Real, page-scoped DOM id as of #300 (scoped, non-shadow rendering) —
81
+ // seki-select-trigger's aria-activedescendant references this directly, so
82
+ // it must stay collision-free across multiple <seki-select> instances on
83
+ // one page. Prefixed with the parent select's own randomized contentId via
84
+ // buildOptionId (shared with seki-select-trigger so both compute the exact
85
+ // same string); falls back to the literal string 'select' if no parent
86
+ // context is available yet (e.g. before componentDidLoad registers it).
82
87
  get optionId() {
83
- return `option-${this.value}`;
88
+ var _a;
89
+ const selectContext = this.getSelectContext();
90
+ return buildOptionId((_a = selectContext === null || selectContext === void 0 ? void 0 : selectContext.contentId) !== null && _a !== void 0 ? _a : 'select', this.value);
84
91
  }
85
92
  componentDidLoad() {
86
93
  this.registerWithParent();
94
+ this.refreshOptionId();
87
95
  this.contextSubscription = subscribeToContextChange(this.el, 'seki-select', this.handleParentContextChange);
88
96
  }
89
97
  connectedCallback() {
@@ -101,6 +109,17 @@ export class SekiSelectOption {
101
109
  selectContext === null || selectContext === void 0 ? void 0 : selectContext.unregisterOption(this.value);
102
110
  this.contextSubscription.unsubscribe();
103
111
  }
112
+ // If render() ran before the parent's selectContext was available (see the
113
+ // 'select' fallback in optionId's getter above), the DOM id was baked in as
114
+ // a non-unique placeholder. componentDidLoad and every later context change
115
+ // both call this to correct it once the real contentId is known — cheap
116
+ // because it's just an attribute write, not a structural re-render.
117
+ refreshOptionId() {
118
+ const optionEl = this.el.querySelector('.option');
119
+ if (optionEl) {
120
+ optionEl.id = this.optionId;
121
+ }
122
+ }
104
123
  registerWithParent() {
105
124
  var _a;
106
125
  const selectContext = this.getSelectContext();
@@ -126,10 +145,10 @@ export class SekiSelectOption {
126
145
  const selectContext = this.getSelectContext();
127
146
  const isSelected = (selectContext === null || selectContext === void 0 ? void 0 : selectContext.isValueSelected(this.value)) || false;
128
147
  const isFocused = (selectContext === null || selectContext === void 0 ? void 0 : selectContext.focusedOptionValue) === this.value;
129
- return (h(Host, { key: '0b45512c5e13b194e043e47fb5b45706e17742f6' }, h("div", { key: '500cf182ac44ea1fced2d7bb095bed0300140fe2', class: "option", role: "option", id: this.optionId, onClick: this.handleOptionClick, onFocus: this.handleOptionFocus, onKeyDown: this.handleOptionKeyDown, "aria-selected": isSelected ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : undefined, "data-selected": isSelected ? 'true' : undefined, "data-focused": isFocused ? 'true' : undefined, "data-disabled": this.disabled ? 'true' : undefined, tabindex: -1 }, h("span", { key: '5760df03485c8a319df0f47e11e7812daf975b90', class: "option-label" }, h("slot", { key: 'e8f30da45607a6ac80c4091e33109134e1860d62' })), h("span", { key: '59646b48367babc22ef134f298194fde8a493085', class: "option-indicator", "aria-hidden": "true" }, h("slot", { key: '7729ba2b454001bb5d71f5c3af7aaf2702999df2', name: "selected-indicator" }, h("svg", { key: 'ac6b0035fae24b5532e6c14ee2339b618abcb1e8', width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: 'ec5d88d49bd03bbafde5a7bfcd746665cd5e02c6', d: "M10 3L4.5 8.5L2 6", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" })))))));
148
+ return (h(Host, { key: '5e81a06da7048aeea965ec7a1f3be01c38610b14' }, h("div", { key: '3129f00c2f8be28153ea68d4ad3552d5fe5babf2', class: "option", role: "option", id: this.optionId, onClick: this.handleOptionClick, onFocus: this.handleOptionFocus, onKeyDown: this.handleOptionKeyDown, "aria-selected": isSelected ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : undefined, "data-selected": isSelected ? 'true' : undefined, "data-focused": isFocused ? 'true' : undefined, "data-disabled": this.disabled ? 'true' : undefined, tabindex: -1 }, h("span", { key: 'fa3daec06f3f25a8339cde8ed9e19778bec0f515', class: "option-label" }, h("slot", { key: 'd778727c4ad82c8b155c6157b2f2c13f7b20bd56' })), h("span", { key: 'a0103ece80cb3a52cf1aa0afaaab801093fa9118', class: "option-indicator", "aria-hidden": "true" }, h("slot", { key: '6d17a76a97fec448881f315bfbd41714604325f5', name: "selected-indicator" }, h("svg", { key: '1b4066f6025db32d6d0e9c4c6e3cc6ee12844631', width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: 'c60be1174d75bc7f6f96e87d6f90a1ba4185d43e', d: "M10 3L4.5 8.5L2 6", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" })))))));
130
149
  }
131
150
  static get is() { return "seki-select-option"; }
132
- static get encapsulation() { return "shadow"; }
151
+ static get encapsulation() { return "scoped"; }
133
152
  static get originalStyleUrls() {
134
153
  return {
135
154
  "$": ["seki-select-option.css"]
@@ -1,4 +1,5 @@
1
1
  import { Host, h } from "@stencil/core";
2
+ import { buildOptionId } from "../../utils/select";
2
3
  import { syncTheme } from "../../utils/theme-sync";
3
4
  import { subscribeToContextChange } from "../../utils/context-bridge";
4
5
  /**
@@ -124,12 +125,14 @@ export class SekiSelectTrigger {
124
125
  }
125
126
  const isOpen = selectContext.open;
126
127
  const isDisabled = selectContext.disabled;
127
- return (h(Host, null, h("button", { ref: (el) => (this.buttonRef = el), type: "button", class: "trigger-button", role: "combobox", "aria-expanded": isOpen ? 'true' : 'false', "aria-controls": selectContext.contentId, "aria-label": selectContext.ariaLabel, "aria-labelledby": selectContext.ariaLabelledby, "aria-disabled": isDisabled ? 'true' : undefined, disabled: isDisabled, "data-disabled": isDisabled ? 'true' : undefined, "data-state": isOpen ? 'open' : 'closed' }, selectContext.multiple
128
+ return (h(Host, null, h("button", { ref: (el) => (this.buttonRef = el), type: "button", class: "trigger-button", role: "combobox", "aria-expanded": isOpen ? 'true' : 'false', "aria-controls": selectContext.contentId, "aria-activedescendant": selectContext.focusedOptionValue !== null
129
+ ? buildOptionId(selectContext.contentId, selectContext.focusedOptionValue)
130
+ : undefined, "aria-label": selectContext.ariaLabel, "aria-labelledby": selectContext.ariaLabelledby, "aria-disabled": isDisabled ? 'true' : undefined, disabled: isDisabled, "data-disabled": isDisabled ? 'true' : undefined, "data-state": isOpen ? 'open' : 'closed' }, selectContext.multiple
128
131
  ? this.renderMultiValue(selectContext)
129
132
  : this.renderSingleValue(selectContext), h("svg", { class: "trigger-chevron", width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, h("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" })))));
130
133
  }
131
134
  static get is() { return "seki-select-trigger"; }
132
- static get encapsulation() { return "shadow"; }
135
+ static get encapsulation() { return "scoped"; }
133
136
  static get originalStyleUrls() {
134
137
  return {
135
138
  "$": ["seki-select-trigger.css"]