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.
- package/README.md +14 -7
- package/declarations/components/command-palette.d.ts +316 -0
- package/declarations/components/command-palette.d.ts.map +1 -0
- package/declarations/components/dialog.d.ts +51 -1
- package/declarations/components/dialog.d.ts.map +1 -1
- package/declarations/components/resizable/state.d.ts.map +1 -1
- package/declarations/index.d.ts +2 -1
- package/declarations/index.d.ts.map +1 -1
- package/dist/components/command-palette.js +363 -0
- package/dist/components/command-palette.js.map +1 -0
- package/dist/components/dialog.js +50 -1
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/resizable/state.js +154 -120
- package/dist/components/resizable/state.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/command-palette.gts +719 -0
- package/src/components/dialog.gts +71 -1
- package/src/components/resizable/state.ts +180 -126
- package/src/index.ts +2 -1
|
@@ -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
|
|
5
|
-
const
|
|
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.#
|
|
214
|
+
return this.#members().panels;
|
|
160
215
|
}
|
|
161
216
|
|
|
162
217
|
get handles(): HTMLElement[] {
|
|
163
|
-
return this.#
|
|
218
|
+
return this.#members().handles;
|
|
164
219
|
}
|
|
165
220
|
|
|
166
|
-
#
|
|
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
|
|
228
|
+
for (const member of element.querySelectorAll<HTMLElement>(MEMBER_SELECTOR)) {
|
|
229
|
+
if (member.closest(GROUP_SELECTOR) !== element) continue;
|
|
172
230
|
|
|
173
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
299
|
-
|
|
361
|
+
#layout(members: Members = this.#members()): void {
|
|
362
|
+
const { panels, handles } = members;
|
|
363
|
+
|
|
364
|
+
if (this.#drag) this.#drag.members = members;
|
|
300
365
|
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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 =
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
414
|
-
|
|
479
|
+
for (const panel of candidates) {
|
|
480
|
+
const size = this.#sizes.get(panel);
|
|
415
481
|
|
|
416
|
-
|
|
482
|
+
if (size === undefined) continue;
|
|
417
483
|
|
|
418
|
-
|
|
419
|
-
|
|
484
|
+
const inlineGrow = panel.style.flexGrow;
|
|
485
|
+
const impliedPercent = (inlineGrow === '' ? 1 : parseFloat(inlineGrow)) * unit;
|
|
420
486
|
|
|
421
|
-
|
|
487
|
+
if (isSameSize(impliedPercent, size)) continue;
|
|
422
488
|
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
438
|
-
|
|
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
|
-
|
|
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.
|
|
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) =>
|
|
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 (
|
|
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 <
|
|
589
|
+
target = target < prevMin / 2 ? 0 : prevMin;
|
|
549
590
|
} else {
|
|
550
|
-
target = clamp(target,
|
|
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 (!
|
|
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 (
|
|
579
|
-
if (target === 0 && !
|
|
619
|
+
if (prevCollapsible) {
|
|
620
|
+
if (target === 0 && !isCollapsed(prev)) {
|
|
580
621
|
this.#previousSizes.set(prev, basePrevSize);
|
|
581
622
|
}
|
|
582
623
|
|
|
583
|
-
|
|
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
|
|
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.#
|
|
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:
|
|
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',
|
|
628
|
-
handleElement.addEventListener('pointerup',
|
|
629
|
-
handleElement.addEventListener('pointercancel',
|
|
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(
|
|
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
|
|
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
|
|
740
|
+
let toDelta: ((prevSize: number) => number) | null = null;
|
|
688
741
|
|
|
689
742
|
switch (event.key) {
|
|
690
743
|
case 'ArrowLeft':
|
|
691
|
-
if (isHorizontal)
|
|
744
|
+
if (isHorizontal) toDelta = () => -step;
|
|
692
745
|
|
|
693
746
|
break;
|
|
694
747
|
case 'ArrowRight':
|
|
695
|
-
if (isHorizontal)
|
|
748
|
+
if (isHorizontal) toDelta = () => step;
|
|
696
749
|
|
|
697
750
|
break;
|
|
698
751
|
case 'ArrowUp':
|
|
699
|
-
if (!isHorizontal)
|
|
752
|
+
if (!isHorizontal) toDelta = () => -step;
|
|
700
753
|
|
|
701
754
|
break;
|
|
702
755
|
case 'ArrowDown':
|
|
703
|
-
if (!isHorizontal)
|
|
756
|
+
if (!isHorizontal) toDelta = () => step;
|
|
704
757
|
|
|
705
758
|
break;
|
|
706
759
|
case 'Home':
|
|
707
|
-
|
|
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
|
-
|
|
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 (
|
|
769
|
+
if (!toDelta) return;
|
|
719
770
|
|
|
720
771
|
event.preventDefault();
|
|
721
772
|
|
|
722
|
-
this.#syncSizesFromDOM();
|
|
723
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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';
|