sh3-core 0.10.4 → 0.10.5
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/dist/__test__/fixtures.js +1 -0
- package/dist/layout/LayoutRenderer.browser.test.js +78 -0
- package/dist/layout/LayoutRenderer.svelte +1 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-6-fixed-slots-freezes-the-handle-adjacent-to-a-fixed-pane--dblclick-does-not-collapse-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-6-fixed-slots-hides-the-collapse-widget-on-a-fixed-pane-1.png +0 -0
- package/dist/layout/types.d.ts +8 -0
- package/dist/primitives/ResizableSplitter.svelte +38 -3
- package/dist/primitives/ResizableSplitter.svelte.d.ts +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ export function makeSplitNode(children, overrides = {}) {
|
|
|
54
54
|
sizes: (_b = overrides.sizes) !== null && _b !== void 0 ? _b : children.map(() => 1 / children.length),
|
|
55
55
|
pinned: overrides.pinned,
|
|
56
56
|
collapsed: overrides.collapsed,
|
|
57
|
+
fixed: overrides.fixed,
|
|
57
58
|
children,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
@@ -272,3 +272,81 @@ describe('LayoutRenderer browser — E.5 splitter collapse toggle', () => {
|
|
|
272
272
|
expect((_a = root.collapsed) === null || _a === void 0 ? void 0 : _a[0]).toBe(true);
|
|
273
273
|
});
|
|
274
274
|
});
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// E.6 — fixed[] slots: no collapse widget, frozen handles
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
describe('LayoutRenderer browser — E.6 fixed slots', () => {
|
|
279
|
+
beforeEach(() => { cleanupDOM(); resetFramework(); });
|
|
280
|
+
it('hides the collapse widget on a fixed pane but keeps it on panes with a non-fixed neighbor', async () => {
|
|
281
|
+
stubView();
|
|
282
|
+
registerApp(makeApp({
|
|
283
|
+
manifest: makeAppManifest({ id: 'e6a' }),
|
|
284
|
+
initialLayout: [
|
|
285
|
+
{
|
|
286
|
+
name: 'default',
|
|
287
|
+
tree: makeTree(makeSplitNode([
|
|
288
|
+
makeSlotNode('a', 'test:view'),
|
|
289
|
+
makeSlotNode('b', 'test:view'),
|
|
290
|
+
makeSlotNode('c', 'test:view'),
|
|
291
|
+
], { fixed: [true, false, false] })),
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
}));
|
|
295
|
+
await launchApp('e6a');
|
|
296
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
297
|
+
await settle(30);
|
|
298
|
+
expect(document.querySelector('[data-testid="collapse-toggle-0"]')).toBeNull();
|
|
299
|
+
expect(document.querySelector('[data-testid="collapse-toggle-1"]')).not.toBeNull();
|
|
300
|
+
expect(document.querySelector('[data-testid="collapse-toggle-2"]')).not.toBeNull();
|
|
301
|
+
});
|
|
302
|
+
it('freezes the handle adjacent to a fixed pane: dblclick does not collapse', async () => {
|
|
303
|
+
var _a, _b, _c, _d;
|
|
304
|
+
stubView();
|
|
305
|
+
registerApp(makeApp({
|
|
306
|
+
manifest: makeAppManifest({ id: 'e6b' }),
|
|
307
|
+
initialLayout: [
|
|
308
|
+
{
|
|
309
|
+
name: 'default',
|
|
310
|
+
tree: makeTree(makeSplitNode([makeSlotNode('a', 'test:view'), makeSlotNode('b', 'test:view')], { fixed: [true, false] })),
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
}));
|
|
314
|
+
await launchApp('e6b');
|
|
315
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
316
|
+
await settle(30);
|
|
317
|
+
const handle = document.querySelector('[data-testid="splitter-handle-0"]');
|
|
318
|
+
expect(handle).not.toBeNull();
|
|
319
|
+
expect(handle.classList.contains('frozen')).toBe(true);
|
|
320
|
+
// pointer-events: none blocks Playwright clicks, so dispatch the event
|
|
321
|
+
// directly to verify the handler itself refuses to toggle.
|
|
322
|
+
handle.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }));
|
|
323
|
+
await settle(50);
|
|
324
|
+
const root = layoutStore.root;
|
|
325
|
+
if ((root === null || root === void 0 ? void 0 : root.type) !== 'split')
|
|
326
|
+
throw new Error('expected split root');
|
|
327
|
+
expect((_b = (_a = root.collapsed) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : false).toBe(false);
|
|
328
|
+
expect((_d = (_c = root.collapsed) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : false).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
it('hides the collapse widget on a middle pane whose neighbors are both fixed', async () => {
|
|
331
|
+
stubView();
|
|
332
|
+
registerApp(makeApp({
|
|
333
|
+
manifest: makeAppManifest({ id: 'e6c' }),
|
|
334
|
+
initialLayout: [
|
|
335
|
+
{
|
|
336
|
+
name: 'default',
|
|
337
|
+
tree: makeTree(makeSplitNode([
|
|
338
|
+
makeSlotNode('a', 'test:view'),
|
|
339
|
+
makeSlotNode('b', 'test:view'),
|
|
340
|
+
makeSlotNode('c', 'test:view'),
|
|
341
|
+
], { fixed: [true, false, true] })),
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
}));
|
|
345
|
+
await launchApp('e6c');
|
|
346
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
347
|
+
await settle(30);
|
|
348
|
+
expect(document.querySelector('[data-testid="collapse-toggle-0"]')).toBeNull();
|
|
349
|
+
expect(document.querySelector('[data-testid="collapse-toggle-1"]')).toBeNull();
|
|
350
|
+
expect(document.querySelector('[data-testid="collapse-toggle-2"]')).toBeNull();
|
|
351
|
+
});
|
|
352
|
+
});
|
|
Binary file
|
package/dist/layout/types.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface SplitNode {
|
|
|
21
21
|
pinned?: SizeMode[];
|
|
22
22
|
/** Per-child collapsed state. Omitted means all expanded. */
|
|
23
23
|
collapsed?: boolean[];
|
|
24
|
+
/**
|
|
25
|
+
* Per-child fixed flag. A fixed child has no collapse widget and the
|
|
26
|
+
* resize handles on either side of it are frozen (non-interactive,
|
|
27
|
+
* rendered thinner). A non-fixed child whose every neighbor is fixed
|
|
28
|
+
* also loses its collapse widget — there's nowhere for the freed
|
|
29
|
+
* space to be used.
|
|
30
|
+
*/
|
|
31
|
+
fixed?: boolean[];
|
|
24
32
|
/** Ordered child nodes. Length must equal `sizes` length. */
|
|
25
33
|
children: LayoutNode[];
|
|
26
34
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
sizes,
|
|
29
29
|
pinned,
|
|
30
30
|
collapsed,
|
|
31
|
+
fixed,
|
|
31
32
|
count,
|
|
32
33
|
pane,
|
|
33
34
|
onResize,
|
|
@@ -46,6 +47,13 @@
|
|
|
46
47
|
pinned?: SizeMode[];
|
|
47
48
|
/** Per-pane collapsed state. Omitted entries default to false. */
|
|
48
49
|
collapsed?: boolean[];
|
|
50
|
+
/**
|
|
51
|
+
* Per-pane fixed flag. A fixed pane has no collapse widget and
|
|
52
|
+
* the handles on either side of it are frozen (non-interactive,
|
|
53
|
+
* rendered thinner). A non-fixed pane whose every neighbor is
|
|
54
|
+
* fixed also loses its collapse widget.
|
|
55
|
+
*/
|
|
56
|
+
fixed?: boolean[];
|
|
49
57
|
/** Number of panes — `sizes.length` should match. */
|
|
50
58
|
count: number;
|
|
51
59
|
/** Snippet invoked once per pane with the pane index. */
|
|
@@ -67,6 +75,15 @@
|
|
|
67
75
|
|
|
68
76
|
const modeOf = (i: number): SizeMode => pinned?.[i] ?? 'fr';
|
|
69
77
|
const isCollapsed = (i: number): boolean => collapsed?.[i] ?? false;
|
|
78
|
+
const isFixed = (i: number): boolean => fixed?.[i] ?? false;
|
|
79
|
+
const isHandleFrozen = (i: number): boolean => isFixed(i) || isFixed(i + 1);
|
|
80
|
+
|
|
81
|
+
function canCollapse(i: number): boolean {
|
|
82
|
+
if (isFixed(i)) return false;
|
|
83
|
+
const left = i > 0 ? !isFixed(i - 1) : false;
|
|
84
|
+
const right = i < count - 1 ? !isFixed(i + 1) : false;
|
|
85
|
+
return left || right;
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
/** CSS `flex` shorthand for pane i. */
|
|
72
89
|
function flexFor(i: number): string {
|
|
@@ -88,8 +105,9 @@
|
|
|
88
105
|
let drag: DragState | null = $state(null);
|
|
89
106
|
|
|
90
107
|
function beginDrag(e: PointerEvent, handleIndex: number) {
|
|
91
|
-
// Disable resize handles adjacent to collapsed panes.
|
|
108
|
+
// Disable resize handles adjacent to collapsed or fixed panes.
|
|
92
109
|
if (isCollapsed(handleIndex) || isCollapsed(handleIndex + 1)) return;
|
|
110
|
+
if (isHandleFrozen(handleIndex)) return;
|
|
93
111
|
|
|
94
112
|
e.preventDefault();
|
|
95
113
|
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
|
@@ -198,12 +216,13 @@
|
|
|
198
216
|
<span class="collapse-icon">{direction === 'horizontal' ? '▸' : '▾'}</span>
|
|
199
217
|
</button>
|
|
200
218
|
{:else}
|
|
201
|
-
{#if onCollapseToggle}
|
|
219
|
+
{#if onCollapseToggle && canCollapse(i)}
|
|
202
220
|
<button
|
|
203
221
|
type="button"
|
|
204
222
|
class="collapse-header expanded"
|
|
205
223
|
class:horizontal={direction === 'horizontal'}
|
|
206
224
|
class:vertical={direction === 'vertical'}
|
|
225
|
+
data-testid="collapse-toggle-{i}"
|
|
207
226
|
onclick={() => onCollapseToggle?.(i, true)}
|
|
208
227
|
aria-label="Collapse pane"
|
|
209
228
|
>
|
|
@@ -221,12 +240,17 @@
|
|
|
221
240
|
class="splitter-handle"
|
|
222
241
|
class:dragging={drag?.handleIndex === i}
|
|
223
242
|
class:disabled={isCollapsed(i) || isCollapsed(i + 1)}
|
|
243
|
+
class:frozen={isHandleFrozen(i)}
|
|
224
244
|
data-testid="splitter-handle-{i}"
|
|
225
245
|
onpointerdown={(e) => beginDrag(e, i)}
|
|
226
246
|
onpointermove={moveDrag}
|
|
227
247
|
onpointerup={endDrag}
|
|
228
248
|
onpointercancel={endDrag}
|
|
229
|
-
ondblclick={() =>
|
|
249
|
+
ondblclick={() => {
|
|
250
|
+
if (isHandleFrozen(i)) return;
|
|
251
|
+
if (!canCollapse(i) && !isCollapsed(i)) return;
|
|
252
|
+
onCollapseToggle?.(i, !isCollapsed(i));
|
|
253
|
+
}}
|
|
230
254
|
role="separator"
|
|
231
255
|
aria-orientation={direction === 'horizontal' ? 'vertical' : 'horizontal'}
|
|
232
256
|
></div>
|
|
@@ -323,6 +347,15 @@
|
|
|
323
347
|
cursor: default;
|
|
324
348
|
pointer-events: none;
|
|
325
349
|
}
|
|
350
|
+
.splitter-handle.frozen {
|
|
351
|
+
cursor: default;
|
|
352
|
+
pointer-events: none;
|
|
353
|
+
background: var(--shell-border);
|
|
354
|
+
opacity: 0.5;
|
|
355
|
+
}
|
|
356
|
+
.splitter-handle.frozen:hover {
|
|
357
|
+
background: var(--shell-border);
|
|
358
|
+
}
|
|
326
359
|
|
|
327
360
|
.horizontal > .splitter-handle {
|
|
328
361
|
width: 4px;
|
|
@@ -332,4 +365,6 @@
|
|
|
332
365
|
height: 4px;
|
|
333
366
|
cursor: row-resize;
|
|
334
367
|
}
|
|
368
|
+
.horizontal > .splitter-handle.frozen { width: 1px; }
|
|
369
|
+
.vertical > .splitter-handle.frozen { height: 1px; }
|
|
335
370
|
</style>
|
|
@@ -14,6 +14,13 @@ type $$ComponentProps = {
|
|
|
14
14
|
pinned?: SizeMode[];
|
|
15
15
|
/** Per-pane collapsed state. Omitted entries default to false. */
|
|
16
16
|
collapsed?: boolean[];
|
|
17
|
+
/**
|
|
18
|
+
* Per-pane fixed flag. A fixed pane has no collapse widget and
|
|
19
|
+
* the handles on either side of it are frozen (non-interactive,
|
|
20
|
+
* rendered thinner). A non-fixed pane whose every neighbor is
|
|
21
|
+
* fixed also loses its collapse widget.
|
|
22
|
+
*/
|
|
23
|
+
fixed?: boolean[];
|
|
17
24
|
/** Number of panes — `sizes.length` should match. */
|
|
18
25
|
count: number;
|
|
19
26
|
/** Snippet invoked once per pane with the pane index. */
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.10.
|
|
2
|
+
export declare const VERSION = "0.10.5";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.10.
|
|
2
|
+
export const VERSION = '0.10.5';
|