ember-primitives 0.61.0 → 0.62.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.
@@ -228,7 +228,77 @@ class ModalDialog extends Component<Signature> {
228
228
  };
229
229
  }
230
230
 
231
+ export interface SimpleSignature {
232
+ Element: HTMLDialogElement;
233
+ Blocks: {
234
+ default: [
235
+ {
236
+ /**
237
+ * Opens the dialog, modally.
238
+ */
239
+ open: () => void;
240
+ /**
241
+ * Closes the dialog.
242
+ */
243
+ close: () => void;
244
+ },
245
+ ];
246
+ };
247
+ }
248
+
249
+ /**
250
+ * A modal `<dialog>` around whatever you put in it, and the two things needed
251
+ * to drive it.
252
+ *
253
+ * ```gjs
254
+ * <Dialog as |d|>
255
+ * <CommandPalette @onSelect={{d.close}} @onOpen={{d.open}} @hotkey="mod+k" />
256
+ * </Dialog>
257
+ * ```
258
+ *
259
+ * The element is here, so a trigger cannot be: whatever opens this either
260
+ * lives inside it, or is a key combination. For a dialog opened by a button
261
+ * beside it, use `<Modal>`, which hands you the element to place.
262
+ *
263
+ * <kbd>Escape</kbd> closes it and focus returns to whatever opened it, which
264
+ * is the `<dialog>` element's own behaviour. Set `closedby` to change which
265
+ * actions dismiss it.
266
+ */
267
+ export class Dialog extends Component<SimpleSignature> {
268
+ /**
269
+ * A plain field rather than tracked state: it is read when somebody calls
270
+ * `open` or `close`, never while rendering, so nothing has to settle in a
271
+ * second pass.
272
+ */
273
+ #element: HTMLDialogElement | undefined;
274
+
275
+ register = eModifier((element: HTMLDialogElement) => {
276
+ this.#element = element;
277
+
278
+ return () => {
279
+ this.#element = undefined;
280
+ };
281
+ });
282
+
283
+ /**
284
+ * `showModal` on an open dialog, and `close` on a closed one, are both
285
+ * no-ops per spec, so neither needs guarding here.
286
+ */
287
+ open = () => {
288
+ this.#element?.showModal();
289
+ };
290
+
291
+ close = () => {
292
+ this.#element?.close();
293
+ };
294
+
295
+ <template>
296
+ <dialog {{this.register}} ...attributes>
297
+ {{yield (hash open=this.open close=this.close)}}
298
+ </dialog>
299
+ </template>
300
+ }
301
+
231
302
  export const Modal = ModalDialog;
232
- export const Dialog = ModalDialog;
233
303
 
234
304
  export default ModalDialog;
@@ -1,8 +1,9 @@
1
1
  export type Orientation = 'horizontal' | 'vertical';
2
2
 
3
3
  const GROUP_SELECTOR = '.ember-primitives__resizable';
4
- const PANEL_SELECTOR = '.ember-primitives__resizable__panel';
5
- const HANDLE_SELECTOR = '.ember-primitives__resizable__handle';
4
+ const PANEL_CLASS = 'ember-primitives__resizable__panel';
5
+ const HANDLE_CLASS = 'ember-primitives__resizable__handle';
6
+ const MEMBER_SELECTOR = `.${PANEL_CLASS}, .${HANDLE_CLASS}`;
6
7
 
7
8
  const DEFAULT_MIN = 0;
8
9
  const DEFAULT_MAX = 100;
@@ -53,6 +54,54 @@ function sameMembers(a: HTMLElement[], b: HTMLElement[]): boolean {
53
54
  return a.length === b.length && a.every((element, index) => element === b[index]);
54
55
  }
55
56
 
57
+ function precedes(a: Element, b: Element): boolean {
58
+ return Boolean(b.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_PRECEDING);
59
+ }
60
+
61
+ /**
62
+ * The `data-collapsed` attribute is the source of truth for collapse
63
+ * state (it is also the styling hook consumers use).
64
+ */
65
+ function isCollapsed(panel: HTMLElement): boolean {
66
+ return isCollapsible(panel) && panel.hasAttribute('data-collapsed');
67
+ }
68
+
69
+ function setCollapsed(panel: HTMLElement, collapsed: boolean): void {
70
+ if (collapsed === panel.hasAttribute('data-collapsed')) return;
71
+
72
+ if (collapsed) {
73
+ panel.setAttribute('data-collapsed', '');
74
+ } else {
75
+ panel.removeAttribute('data-collapsed');
76
+ }
77
+ }
78
+
79
+ /**
80
+ * The panels immediately before and after the given handle element,
81
+ * in document order.
82
+ */
83
+ function neighborsOf(
84
+ handleElement: HTMLElement,
85
+ panels: HTMLElement[]
86
+ ): [HTMLElement | null, HTMLElement | null] {
87
+ let prev: HTMLElement | null = null;
88
+ let next: HTMLElement | null = null;
89
+
90
+ for (const panel of panels) {
91
+ const position = handleElement.compareDocumentPosition(panel);
92
+
93
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) {
94
+ prev = panel;
95
+ } else if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
96
+ next = panel;
97
+
98
+ break;
99
+ }
100
+ }
101
+
102
+ return [prev, next];
103
+ }
104
+
56
105
  /**
57
106
  * Sizes are floats that get re-derived (measurement, normalization),
58
107
  * so "unchanged" means within a small tolerance.
@@ -80,6 +129,11 @@ function setAttribute(element: Element, name: string, value: string): void {
80
129
  }
81
130
  }
82
131
 
132
+ interface Members {
133
+ panels: HTMLElement[];
134
+ handles: HTMLElement[];
135
+ }
136
+
83
137
  interface DragState {
84
138
  prev: HTMLElement;
85
139
  next: HTMLElement;
@@ -91,6 +145,7 @@ interface DragState {
91
145
  * used to convert px deltas to % deltas.
92
146
  */
93
147
  totalPx: number;
148
+ members: Members;
94
149
  move: (event: PointerEvent) => void;
95
150
  end: (event: PointerEvent) => void;
96
151
  }
@@ -156,21 +211,27 @@ export class GroupState {
156
211
  * Panels of nested groups belong to their own group, not this one.
157
212
  */
158
213
  get panels(): HTMLElement[] {
159
- return this.#query(PANEL_SELECTOR);
214
+ return this.#members().panels;
160
215
  }
161
216
 
162
217
  get handles(): HTMLElement[] {
163
- return this.#query(HANDLE_SELECTOR);
218
+ return this.#members().handles;
164
219
  }
165
220
 
166
- #query(selector: string): HTMLElement[] {
221
+ #members(): Members {
222
+ const panels: HTMLElement[] = [];
223
+ const handles: HTMLElement[] = [];
167
224
  const element = this.element;
168
225
 
169
- if (!element) return [];
226
+ if (!element) return { panels, handles };
170
227
 
171
- const all = Array.from(element.querySelectorAll<HTMLElement>(selector));
228
+ for (const member of element.querySelectorAll<HTMLElement>(MEMBER_SELECTOR)) {
229
+ if (member.closest(GROUP_SELECTOR) !== element) continue;
172
230
 
173
- return all.filter((candidate) => candidate.closest(GROUP_SELECTOR) === element);
231
+ (member.classList.contains(PANEL_CLASS) ? panels : handles).push(member);
232
+ }
233
+
234
+ return { panels, handles };
174
235
  }
175
236
 
176
237
  get sizes(): number[] {
@@ -229,7 +290,9 @@ export class GroupState {
229
290
  }
230
291
 
231
292
  for (const [group, orientationChanged] of affected) {
232
- if (orientationChanged || group.#membershipChanged()) group.#layout();
293
+ const members = group.#members();
294
+
295
+ if (orientationChanged || group.#membershipChanged(members)) group.#layout(members);
233
296
  }
234
297
  }
235
298
 
@@ -283,9 +346,10 @@ export class GroupState {
283
346
  };
284
347
  };
285
348
 
286
- #membershipChanged(): boolean {
349
+ #membershipChanged(members: Members): boolean {
287
350
  return (
288
- !sameMembers(this.panels, this.#knownPanels) || !sameMembers(this.handles, this.#knownHandles)
351
+ !sameMembers(members.panels, this.#knownPanels) ||
352
+ !sameMembers(members.handles, this.#knownHandles)
289
353
  );
290
354
  }
291
355
 
@@ -294,20 +358,23 @@ export class GroupState {
294
358
  * keep it; new panels take a share of the remaining space; everything
295
359
  * is normalized to 100.
296
360
  */
297
- #layout(): void {
298
- const panels = this.panels;
299
- const handles = this.handles;
361
+ #layout(members: Members = this.#members()): void {
362
+ const { panels, handles } = members;
363
+
364
+ if (this.#drag) this.#drag.members = members;
300
365
 
301
- if (!sameMembers(panels, this.#knownPanels)) this.#knownPanels = panels;
302
- if (!sameMembers(handles, this.#knownHandles)) this.#knownHandles = handles;
366
+ this.#knownPanels = panels;
367
+ this.#knownHandles = handles;
303
368
 
304
369
  if (panels.length === 0) return;
305
370
 
306
371
  let changed = false;
307
372
 
308
373
  // forget sizes of panels that left the DOM
309
- for (const known of Array.from(this.#sizes.keys())) {
310
- if (!panels.includes(known)) {
374
+ const current = new Set(panels);
375
+
376
+ for (const known of this.#sizes.keys()) {
377
+ if (!current.has(known)) {
311
378
  this.#sizes.delete(known);
312
379
  changed = true;
313
380
  }
@@ -327,7 +394,7 @@ export class GroupState {
327
394
  continue;
328
395
  }
329
396
 
330
- const size = this.#isCollapsed(panel)
397
+ const size = isCollapsed(panel)
331
398
  ? preferred
332
399
  : clamp(preferred, minSizeOf(panel), maxSizeOf(panel));
333
400
 
@@ -389,9 +456,9 @@ export class GroupState {
389
456
  }
390
457
  }
391
458
 
392
- this.#apply(changedPanels);
459
+ this.#apply(changedPanels, members);
393
460
 
394
- if (changed) this.#notify();
461
+ if (changed) this.#notify(panels);
395
462
  }
396
463
 
397
464
  /**
@@ -402,30 +469,28 @@ export class GroupState {
402
469
  * points, not derived from sizes -- rendered pixel sizes include
403
470
  * borders/padding, so a collapsed panel rarely measures exactly 0.)
404
471
  */
405
- #apply(candidates: HTMLElement[]): void {
406
- if (candidates.length > 0) {
407
- /**
408
- * With no unit fixed yet, nothing has ever been written, so every
409
- * panel renders at the CSS default -- an equal 1/n share.
410
- */
411
- const unit = this.#unit ?? 100 / this.panels.length;
472
+ #apply(candidates: HTMLElement[], members: Members): void {
473
+ /**
474
+ * With no unit fixed yet, nothing has ever been written, so every
475
+ * panel renders at the CSS default -- an equal 1/n share.
476
+ */
477
+ const unit = this.#unit ?? 100 / members.panels.length;
412
478
 
413
- for (const panel of candidates) {
414
- const size = this.#sizes.get(panel);
479
+ for (const panel of candidates) {
480
+ const size = this.#sizes.get(panel);
415
481
 
416
- if (size === undefined) continue;
482
+ if (size === undefined) continue;
417
483
 
418
- const inlineGrow = panel.style.flexGrow;
419
- const impliedPercent = (inlineGrow === '' ? 1 : parseFloat(inlineGrow)) * unit;
484
+ const inlineGrow = panel.style.flexGrow;
485
+ const impliedPercent = (inlineGrow === '' ? 1 : parseFloat(inlineGrow)) * unit;
420
486
 
421
- if (isSameSize(impliedPercent, size)) continue;
487
+ if (isSameSize(impliedPercent, size)) continue;
422
488
 
423
- this.#unit ??= unit;
424
- panel.style.flex = `${size / unit} 1 0px`;
425
- }
489
+ this.#unit ??= unit;
490
+ panel.style.flex = `${size / unit} 1 0px`;
426
491
  }
427
492
 
428
- this.#syncHandles();
493
+ this.#syncHandles(members);
429
494
  }
430
495
 
431
496
  /**
@@ -433,11 +498,23 @@ export class GroupState {
433
498
  * immediately before it. (A splitter between two side-by-side panes
434
499
  * is oriented *vertically*, and vice-versa.)
435
500
  */
436
- #syncHandles(): void {
437
- for (const handle of this.handles) {
438
- const [prev] = this.#neighborsOf(handle);
501
+ #syncHandles(members: Members): void {
502
+ const { panels, handles } = members;
503
+ const ariaOrientation = this.#isHorizontal ? 'vertical' : 'horizontal';
504
+
505
+ let index = 0;
439
506
 
440
- setAttribute(handle, 'aria-orientation', this.#isHorizontal ? 'vertical' : 'horizontal');
507
+ for (const handle of handles) {
508
+ let following = panels[index];
509
+
510
+ while (following && precedes(following, handle)) {
511
+ index++;
512
+ following = panels[index];
513
+ }
514
+
515
+ const prev = panels[index - 1];
516
+
517
+ setAttribute(handle, 'aria-orientation', ariaOrientation);
441
518
 
442
519
  if (!prev) continue;
443
520
 
@@ -450,36 +527,19 @@ export class GroupState {
450
527
  }
451
528
  }
452
529
 
453
- #notify(): void {
454
- this.#options.onLayoutChange()?.(this.sizes);
455
- }
456
-
457
- /**
458
- * The `data-collapsed` attribute is the source of truth for collapse
459
- * state (it is also the styling hook consumers use).
460
- */
461
- #isCollapsed(panel: HTMLElement): boolean {
462
- return isCollapsible(panel) && panel.hasAttribute('data-collapsed');
463
- }
464
-
465
- #setCollapsed(panel: HTMLElement, collapsed: boolean): void {
466
- if (collapsed === panel.hasAttribute('data-collapsed')) return;
467
-
468
- if (collapsed) {
469
- panel.setAttribute('data-collapsed', '');
470
- } else {
471
- panel.removeAttribute('data-collapsed');
472
- }
530
+ #notify(panels: HTMLElement[]): void {
531
+ this.#options.onLayoutChange()?.(panels.map((panel) => this.#sizes.get(panel) ?? 0));
473
532
  }
474
533
 
475
534
  /**
476
535
  * Re-derive percentage sizes from actual rendered pixels.
477
536
  * Corrects any drift (e.g. from CSS min-sizes) before an interaction.
478
537
  */
479
- #syncSizesFromDOM(): void {
480
- const panels = this.panels;
538
+ #syncSizesFromDOM(panels: HTMLElement[], measured?: number[]): void {
481
539
  // collapsed panels are 0 even though their borders/padding measure larger
482
- const px = panels.map((panel) => (this.#isCollapsed(panel) ? 0 : this.#pixelSizeOf(panel)));
540
+ const px = panels.map((panel, index) =>
541
+ isCollapsed(panel) ? 0 : (measured?.[index] ?? this.#pixelSizeOf(panel))
542
+ );
483
543
  const total = px.reduce((sum, value) => sum + value, 0);
484
544
 
485
545
  if (total <= 0) return;
@@ -500,29 +560,6 @@ export class GroupState {
500
560
  return this.#isHorizontal ? box.width : box.height;
501
561
  }
502
562
 
503
- /**
504
- * The panels immediately before and after the given handle element,
505
- * in document order.
506
- */
507
- #neighborsOf(handleElement: HTMLElement): [HTMLElement | null, HTMLElement | null] {
508
- let prev: HTMLElement | null = null;
509
- let next: HTMLElement | null = null;
510
-
511
- for (const panel of this.panels) {
512
- const position = handleElement.compareDocumentPosition(panel);
513
-
514
- if (position & Node.DOCUMENT_POSITION_PRECEDING) {
515
- prev = panel;
516
- } else if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
517
- next = panel;
518
-
519
- break;
520
- }
521
- }
522
-
523
- return [prev, next];
524
- }
525
-
526
563
  /**
527
564
  * Moves the boundary between the two panels by `requestedDelta` (%),
528
565
  * respecting both panels' min/max constraints.
@@ -532,22 +569,26 @@ export class GroupState {
532
569
  next: HTMLElement,
533
570
  basePrevSize: number,
534
571
  baseNextSize: number,
535
- requestedDelta: number
572
+ requestedDelta: number,
573
+ members: Members
536
574
  ): void {
537
575
  const total = basePrevSize + baseNextSize;
576
+ const prevMin = minSizeOf(prev);
577
+ const prevMax = maxSizeOf(prev);
578
+ const prevCollapsible = isCollapsible(prev);
538
579
 
539
580
  let target = basePrevSize + requestedDelta;
540
581
 
541
- if (isCollapsible(prev) && target < minSizeOf(prev)) {
582
+ if (prevCollapsible && target < prevMin) {
542
583
  /**
543
584
  * Collapsible panels snap: below half the minimum they close
544
585
  * entirely; between half and the minimum they hold at the minimum.
545
586
  * (This also keeps a collapsed panel closed when its handle is
546
587
  * dragged further in the closing direction.)
547
588
  */
548
- target = target < minSizeOf(prev) / 2 ? 0 : minSizeOf(prev);
589
+ target = target < prevMin / 2 ? 0 : prevMin;
549
590
  } else {
550
- target = clamp(target, minSizeOf(prev), maxSizeOf(prev));
591
+ target = clamp(target, prevMin, prevMax);
551
592
  }
552
593
 
553
594
  /**
@@ -562,7 +603,7 @@ export class GroupState {
562
603
  target = clamp(target, nextMin, nextMax);
563
604
 
564
605
  // both panels' constraints cannot be satisfied at once
565
- if (!isCollapsible(prev) && (target < minSizeOf(prev) || target > maxSizeOf(prev))) return;
606
+ if (!prevCollapsible && (target < prevMin || target > prevMax)) return;
566
607
 
567
608
  /**
568
609
  * Nothing to do when the clamped result matches the current sizes
@@ -575,30 +616,33 @@ export class GroupState {
575
616
  return;
576
617
  }
577
618
 
578
- if (isCollapsible(prev)) {
579
- if (target === 0 && !this.#isCollapsed(prev)) {
619
+ if (prevCollapsible) {
620
+ if (target === 0 && !isCollapsed(prev)) {
580
621
  this.#previousSizes.set(prev, basePrevSize);
581
622
  }
582
623
 
583
- this.#setCollapsed(prev, target === 0);
624
+ setCollapsed(prev, target === 0);
584
625
  }
585
626
 
586
627
  this.#sizes.set(prev, target);
587
628
  this.#sizes.set(next, total - target);
588
629
 
589
- this.#apply([prev, next]);
590
- this.#notify();
630
+ this.#apply([prev, next], members);
631
+ this.#notify(members.panels);
591
632
  }
592
633
 
593
634
  startDrag(handleElement: HTMLElement, event: PointerEvent): void {
594
635
  if (event.button !== 0) return;
595
636
  if (this.#drag) return;
596
637
 
597
- const [prev, next] = this.#neighborsOf(handleElement);
638
+ const members = this.#members();
639
+ const [prev, next] = neighborsOf(handleElement, members.panels);
598
640
 
599
641
  if (!prev || !next) return;
600
642
 
601
- this.#syncSizesFromDOM();
643
+ const measured = members.panels.map((panel) => this.#pixelSizeOf(panel));
644
+
645
+ this.#syncSizesFromDOM(members.panels, measured);
602
646
 
603
647
  const move = (moveEvent: PointerEvent) => this.#dragMove(moveEvent);
604
648
  const end = (endEvent: PointerEvent) => this.#endDrag(handleElement, endEvent);
@@ -609,7 +653,8 @@ export class GroupState {
609
653
  startPrevSize: this.#sizes.get(prev) ?? 0,
610
654
  startNextSize: this.#sizes.get(next) ?? 0,
611
655
  startCoordinate: this.#isHorizontal ? event.clientX : event.clientY,
612
- totalPx: this.panels.reduce((sum, panel) => sum + this.#pixelSizeOf(panel), 0),
656
+ totalPx: measured.reduce((sum, value) => sum + value, 0),
657
+ members,
613
658
  move,
614
659
  end,
615
660
  };
@@ -624,9 +669,9 @@ export class GroupState {
624
669
  // synthetic events (tests) may not have an active pointer
625
670
  }
626
671
 
627
- handleElement.addEventListener('pointermove', this.#drag.move);
628
- handleElement.addEventListener('pointerup', this.#drag.end);
629
- handleElement.addEventListener('pointercancel', this.#drag.end);
672
+ handleElement.addEventListener('pointermove', move);
673
+ handleElement.addEventListener('pointerup', end);
674
+ handleElement.addEventListener('pointercancel', end);
630
675
  handleElement.setAttribute('data-resizing', '');
631
676
 
632
677
  document.body.style.cursor = this.#isHorizontal ? 'col-resize' : 'row-resize';
@@ -642,7 +687,14 @@ export class GroupState {
642
687
  const coordinate = this.#isHorizontal ? event.clientX : event.clientY;
643
688
  const deltaPercent = ((coordinate - drag.startCoordinate) / drag.totalPx) * 100;
644
689
 
645
- this.#applyDelta(drag.prev, drag.next, drag.startPrevSize, drag.startNextSize, deltaPercent);
690
+ this.#applyDelta(
691
+ drag.prev,
692
+ drag.next,
693
+ drag.startPrevSize,
694
+ drag.startNextSize,
695
+ deltaPercent,
696
+ drag.members
697
+ );
646
698
  }
647
699
 
648
700
  #endDrag(handleElement: HTMLElement, event: PointerEvent): void {
@@ -671,12 +723,13 @@ export class GroupState {
671
723
  * Keyboard support for the WAI-ARIA window-splitter pattern.
672
724
  */
673
725
  handleKeyDown(handleElement: HTMLElement, event: KeyboardEvent): void {
674
- const [prev, next] = this.#neighborsOf(handleElement);
726
+ const members = this.#members();
727
+ const [prev, next] = neighborsOf(handleElement, members.panels);
675
728
 
676
729
  if (!prev || !next) return;
677
730
 
678
731
  if (event.key === 'Enter') {
679
- this.#toggleCollapse(prev, next);
732
+ this.#toggleCollapse(prev, next, members);
680
733
 
681
734
  return;
682
735
  }
@@ -684,43 +737,44 @@ export class GroupState {
684
737
  const step = event.shiftKey ? KEYBOARD_STEP_COARSE : KEYBOARD_STEP;
685
738
  const isHorizontal = this.#isHorizontal;
686
739
 
687
- let delta: number | null = null;
740
+ let toDelta: ((prevSize: number) => number) | null = null;
688
741
 
689
742
  switch (event.key) {
690
743
  case 'ArrowLeft':
691
- if (isHorizontal) delta = -step;
744
+ if (isHorizontal) toDelta = () => -step;
692
745
 
693
746
  break;
694
747
  case 'ArrowRight':
695
- if (isHorizontal) delta = step;
748
+ if (isHorizontal) toDelta = () => step;
696
749
 
697
750
  break;
698
751
  case 'ArrowUp':
699
- if (!isHorizontal) delta = -step;
752
+ if (!isHorizontal) toDelta = () => -step;
700
753
 
701
754
  break;
702
755
  case 'ArrowDown':
703
- if (!isHorizontal) delta = step;
756
+ if (!isHorizontal) toDelta = () => step;
704
757
 
705
758
  break;
706
759
  case 'Home':
707
- this.#syncSizesFromDOM();
708
- delta = minSizeOf(prev) - (this.#sizes.get(prev) ?? 0);
760
+ toDelta = (prevSize) => minSizeOf(prev) - prevSize;
709
761
 
710
762
  break;
711
763
  case 'End':
712
- this.#syncSizesFromDOM();
713
- delta = maxSizeOf(prev) - (this.#sizes.get(prev) ?? 0);
764
+ toDelta = (prevSize) => maxSizeOf(prev) - prevSize;
714
765
 
715
766
  break;
716
767
  }
717
768
 
718
- if (delta === null) return;
769
+ if (!toDelta) return;
719
770
 
720
771
  event.preventDefault();
721
772
 
722
- this.#syncSizesFromDOM();
723
- this.#applyDelta(prev, next, this.#sizes.get(prev) ?? 0, this.#sizes.get(next) ?? 0, delta);
773
+ this.#syncSizesFromDOM(members.panels);
774
+
775
+ const prevSize = this.#sizes.get(prev) ?? 0;
776
+
777
+ this.#applyDelta(prev, next, prevSize, this.#sizes.get(next) ?? 0, toDelta(prevSize), members);
724
778
  }
725
779
 
726
780
  /**
@@ -729,15 +783,15 @@ export class GroupState {
729
783
  *
730
784
  * Only does anything when the preceding panel is `@collapsible`.
731
785
  */
732
- #toggleCollapse(prev: HTMLElement, next: HTMLElement): void {
786
+ #toggleCollapse(prev: HTMLElement, next: HTMLElement, members: Members): void {
733
787
  if (!isCollapsible(prev)) return;
734
788
 
735
- this.#syncSizesFromDOM();
789
+ this.#syncSizesFromDOM(members.panels);
736
790
 
737
791
  const prevSize = this.#sizes.get(prev) ?? 0;
738
792
  const nextSize = this.#sizes.get(next) ?? 0;
739
793
 
740
- if (this.#isCollapsed(prev)) {
794
+ if (isCollapsed(prev)) {
741
795
  const preferred =
742
796
  this.#previousSizes.get(prev) ?? requestedSizeOf(prev) ?? Math.max(minSizeOf(prev), 10);
743
797
  const available = nextSize - minSizeOf(next);
@@ -745,17 +799,17 @@ export class GroupState {
745
799
 
746
800
  if (restored <= 0) return;
747
801
 
748
- this.#setCollapsed(prev, false);
802
+ setCollapsed(prev, false);
749
803
  this.#sizes.set(prev, restored);
750
804
  this.#sizes.set(next, nextSize - restored);
751
805
  } else {
752
806
  this.#previousSizes.set(prev, prevSize);
753
- this.#setCollapsed(prev, true);
807
+ setCollapsed(prev, true);
754
808
  this.#sizes.set(prev, 0);
755
809
  this.#sizes.set(next, nextSize + prevSize);
756
810
  }
757
811
 
758
- this.#apply([prev, next]);
759
- this.#notify();
812
+ this.#apply([prev, next], members);
813
+ this.#notify(members.panels);
760
814
  }
761
815
  }
package/src/index.ts CHANGED
@@ -20,7 +20,8 @@ export type {
20
20
  } from './components/accordion/public.ts';
21
21
  export { Avatar } from './components/avatar.gts';
22
22
  export { Breadcrumb } from './components/breadcrumb.gts';
23
- export { Dialog, Dialog as Modal } from './components/dialog.gts';
23
+ export { CommandPalette } from './components/command-palette.gts';
24
+ export { Dialog, Modal } from './components/dialog.gts';
24
25
  export { Drawer } from './components/drawer.gts';
25
26
  export { ExternalLink } from './components/external-link.gts';
26
27
  export { Form } from './components/form.gts';