@syntrologie/adapt-content 2.8.0-canary.93 → 2.8.0-canary.95
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/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.d.ts +9 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPickerLit.js +27 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShell.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShell.js +28 -17
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.js +18 -17
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlightLit.d.ts +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.d.ts +110 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.d.ts.map +1 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.js +476 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/index.d.ts +2 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/index.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/index.js +1 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.d.ts +1 -0
- package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/lit-elements.js +1 -0
- package/node_modules/@syntrologie/shared-editor-ui/package.json +1 -0
- package/package.json +1 -1
|
@@ -54,9 +54,17 @@ export declare class AnchorPickerLit extends LitElement {
|
|
|
54
54
|
updated(changed: Map<string, unknown>): void;
|
|
55
55
|
private _attachDocumentListeners;
|
|
56
56
|
private _detachDocumentListeners;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the shadow host this picker lives inside, if any. When the picker
|
|
59
|
+
* is mounted inside a shadow root (e.g. the editor SDK mounts inside
|
|
60
|
+
* `<smart-canvas>`'s shadow), `document.elementFromPoint` returns the shadow
|
|
61
|
+
* *host*, not the host page element beneath. Temporarily disabling pointer
|
|
62
|
+
* events on the host lets hit-testing fall through to the host page.
|
|
63
|
+
*/
|
|
64
|
+
private _getShadowHost;
|
|
57
65
|
private _handleMouseMove;
|
|
58
66
|
private _handleClick;
|
|
59
67
|
private _handleKeyDown;
|
|
60
|
-
render(): import("lit-html").TemplateResult<1
|
|
68
|
+
render(): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
61
69
|
}
|
|
62
70
|
//# sourceMappingURL=AnchorPickerLit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnchorPickerLit.d.ts","sourceRoot":"","sources":["../../src/components/AnchorPickerLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAQ,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAahD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,UAAU;;;;;;;;;;;;;;;;;;;MAOxB;IAEF,QAAQ,UAAS;IACjB,iBAAiB,UAAS;IAC1B,eAAe,EAAE,MAAM,CAA4B;IAGnD,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,gBAAgB,CAAM;IAG9B,OAAO,CAAC,UAAU,CAA+B;IAIxC,gBAAgB;IAIhB,iBAAiB,IAAI,IAAI;IAOzB,oBAAoB,IAAI,IAAI;IAK5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAiBrD,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,gBAAgB,
|
|
1
|
+
{"version":3,"file":"AnchorPickerLit.d.ts","sourceRoot":"","sources":["../../src/components/AnchorPickerLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAQ,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAahD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,UAAU;;;;;;;;;;;;;;;;;;;MAOxB;IAEF,QAAQ,UAAS;IACjB,iBAAiB,UAAS;IAC1B,eAAe,EAAE,MAAM,CAA4B;IAGnD,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,gBAAgB,CAAM;IAG9B,OAAO,CAAC,UAAU,CAA+B;IAIxC,gBAAgB;IAIhB,iBAAiB,IAAI,IAAI;IAOzB,oBAAoB,IAAI,IAAI;IAK5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAiBrD,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,wBAAwB;IAQhC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,gBAAgB,CA+CtB;IAEF,OAAO,CAAC,YAAY,CAwBlB;IAEF,OAAO,CAAC,cAAc,CAUpB;IAIO,MAAM;CA0GhB"}
|
|
@@ -38,16 +38,28 @@ export class AnchorPickerLit extends LitElement {
|
|
|
38
38
|
this._hoveredSelector = '';
|
|
39
39
|
// DOM ref for the overlay
|
|
40
40
|
this._overlayEl = null;
|
|
41
|
-
// ---- EVENT HANDLERS ----
|
|
42
41
|
this._handleMouseMove = (e) => {
|
|
43
42
|
const overlay = this._overlayEl;
|
|
43
|
+
const shadowHost = this._getShadowHost();
|
|
44
44
|
// In passthrough mode the overlay is already pointerEvents:'none',
|
|
45
45
|
// so elementFromPoint can see through it -- no toggle needed.
|
|
46
46
|
// In non-passthrough mode, temporarily disable to detect elements underneath.
|
|
47
47
|
if (overlay && !this.passthroughClicks) {
|
|
48
48
|
overlay.style.pointerEvents = 'none';
|
|
49
49
|
}
|
|
50
|
+
// If we're inside a shadow root, temporarily hide the host so
|
|
51
|
+
// elementFromPoint can reach the host page underneath. `pointer-events`
|
|
52
|
+
// doesn't work here because any fixed-position descendant inside the
|
|
53
|
+
// shadow tree with pointer-events:auto (e.g. a canvas overlay) causes
|
|
54
|
+
// elementFromPoint to return the shadow host regardless of what the
|
|
55
|
+
// host's own pointer-events is. `visibility:hidden` removes the entire
|
|
56
|
+
// subtree from hit-testing without triggering layout reflow.
|
|
57
|
+
const prevHostVis = shadowHost?.style.visibility;
|
|
58
|
+
if (shadowHost)
|
|
59
|
+
shadowHost.style.visibility = 'hidden';
|
|
50
60
|
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
|
|
61
|
+
if (shadowHost)
|
|
62
|
+
shadowHost.style.visibility = prevHostVis ?? '';
|
|
51
63
|
if (overlay && !this.passthroughClicks) {
|
|
52
64
|
overlay.style.pointerEvents = 'auto';
|
|
53
65
|
}
|
|
@@ -141,6 +153,20 @@ export class AnchorPickerLit extends LitElement {
|
|
|
141
153
|
document.removeEventListener('click', this._handleClick, true);
|
|
142
154
|
document.removeEventListener('keydown', this._handleKeyDown, true);
|
|
143
155
|
}
|
|
156
|
+
// ---- EVENT HANDLERS ----
|
|
157
|
+
/**
|
|
158
|
+
* Resolve the shadow host this picker lives inside, if any. When the picker
|
|
159
|
+
* is mounted inside a shadow root (e.g. the editor SDK mounts inside
|
|
160
|
+
* `<smart-canvas>`'s shadow), `document.elementFromPoint` returns the shadow
|
|
161
|
+
* *host*, not the host page element beneath. Temporarily disabling pointer
|
|
162
|
+
* events on the host lets hit-testing fall through to the host page.
|
|
163
|
+
*/
|
|
164
|
+
_getShadowHost() {
|
|
165
|
+
const root = this.getRootNode();
|
|
166
|
+
if (root instanceof ShadowRoot)
|
|
167
|
+
return root.host;
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
144
170
|
// ---- RENDER ----
|
|
145
171
|
render() {
|
|
146
172
|
if (!this.isActive)
|
package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShell.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorPanelShell.d.ts","sourceRoot":"","sources":["../../src/components/EditorPanelShell.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"EditorPanelShell.d.ts","sourceRoot":"","sources":["../../src/components/EditorPanelShell.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AA8MD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,QAAQ,EACR,OAAO,EACP,MAAmB,EACnB,QAAQ,GACT,EAAE,qBAAqB,2CAwSvB"}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* EditorPanelShell — moveable, resizable wrapper for editor sidebars.
|
|
4
|
+
*
|
|
5
|
+
* Renders:
|
|
6
|
+
* 1. A fixed FAB button (Syntrologie logo) at the panel's bottom-left corner.
|
|
7
|
+
* Click toggles open/minimized. Drag moves the entire panel.
|
|
8
|
+
* 2. A resizable panel container with semi-transparent background and backdrop blur.
|
|
9
|
+
* Supports docked (left/right edge, full height) and free-floating modes.
|
|
10
|
+
* Position, size, and dock state persist in localStorage.
|
|
11
|
+
*/
|
|
12
|
+
import { panelShell } from '@syntro/design-system';
|
|
2
13
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
14
|
import { cn } from '../cn';
|
|
4
15
|
// =============================================================================
|
|
5
16
|
// Constants
|
|
6
17
|
// =============================================================================
|
|
7
18
|
const STORAGE_KEY = 'syntro:editor-panel';
|
|
8
|
-
const MIN_WIDTH =
|
|
9
|
-
const MIN_HEIGHT =
|
|
10
|
-
const FAB_SIZE =
|
|
11
|
-
const FAB_INSET =
|
|
12
|
-
const SNAP_THRESHOLD =
|
|
13
|
-
const DRAG_THRESHOLD =
|
|
14
|
-
const HANDLE_SIZE =
|
|
19
|
+
const MIN_WIDTH = panelShell.behavior.minWidth;
|
|
20
|
+
const MIN_HEIGHT = panelShell.behavior.minHeight;
|
|
21
|
+
const FAB_SIZE = panelShell.fab.size;
|
|
22
|
+
const FAB_INSET = panelShell.fab.inset;
|
|
23
|
+
const SNAP_THRESHOLD = panelShell.behavior.snapThreshold;
|
|
24
|
+
const DRAG_THRESHOLD = panelShell.behavior.dragThreshold;
|
|
25
|
+
const HANDLE_SIZE = panelShell.behavior.handleSize;
|
|
15
26
|
// =============================================================================
|
|
16
27
|
// Logo
|
|
17
28
|
// =============================================================================
|
|
@@ -313,12 +324,14 @@ export function EditorPanelShell({ isOpen, onToggle, panelId, zIndex = 214748364
|
|
|
313
324
|
const panelStyle = {
|
|
314
325
|
position: 'fixed',
|
|
315
326
|
zIndex,
|
|
316
|
-
background:
|
|
327
|
+
background: panelShell.background,
|
|
328
|
+
backdropFilter: `blur(${panelShell.backdropBlur})`,
|
|
329
|
+
WebkitBackdropFilter: `blur(${panelShell.backdropBlur})`,
|
|
317
330
|
boxShadow: isDocked
|
|
318
331
|
? geometry.docked === 'right'
|
|
319
|
-
?
|
|
320
|
-
:
|
|
321
|
-
:
|
|
332
|
+
? panelShell.shadows.dockedRight
|
|
333
|
+
: panelShell.shadows.dockedLeft
|
|
334
|
+
: panelShell.shadows.floating,
|
|
322
335
|
// Prevent wheel scroll from chaining to the page behind the panel.
|
|
323
336
|
// overflow-y: auto makes this a scroll container so overscroll-behavior applies.
|
|
324
337
|
overflowY: 'auto',
|
|
@@ -350,16 +363,14 @@ export function EditorPanelShell({ isOpen, onToggle, panelId, zIndex = 214748364
|
|
|
350
363
|
width: FAB_SIZE,
|
|
351
364
|
height: FAB_SIZE,
|
|
352
365
|
borderRadius: '50%',
|
|
353
|
-
border:
|
|
366
|
+
border: panelShell.fab.border,
|
|
354
367
|
cursor: 'grab',
|
|
355
368
|
display: 'flex',
|
|
356
369
|
alignItems: 'center',
|
|
357
370
|
justifyContent: 'center',
|
|
358
|
-
background:
|
|
359
|
-
color:
|
|
360
|
-
boxShadow: isOpen
|
|
361
|
-
? '0 4px 24px rgba(0,0,0,0.6), 0 0 0 2px rgba(255,255,255,0.08)'
|
|
362
|
-
: '0 4px 24px rgba(0,0,0,0.6)',
|
|
371
|
+
background: panelShell.fab.background,
|
|
372
|
+
color: panelShell.fab.color,
|
|
373
|
+
boxShadow: isOpen ? panelShell.fab.shadowOpen : panelShell.fab.shadowClosed,
|
|
363
374
|
transition: 'box-shadow 150ms ease',
|
|
364
375
|
touchAction: 'none',
|
|
365
376
|
userSelect: 'none',
|
package/node_modules/@syntrologie/shared-editor-ui/dist/components/EditorPanelShellLit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorPanelShellLit.d.ts","sourceRoot":"","sources":["../../src/components/EditorPanelShellLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"EditorPanelShellLit.d.ts","sourceRoot":"","sources":["../../src/components/EditorPanelShellLit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAQ,UAAU,EAAW,MAAM,KAAK,CAAC;AA8OhD,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,OAAgB,UAAU;;;;;;;;;;;;;MAIxB;IAEF,MAAM,UAAS;IACf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,SAAc;IAGpB,OAAO,CAAC,SAAS,CAAuC;IAGxD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAAkC;IAGhD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,WAAW,CAA8B;IAGjD,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,eAAe,CAAuC;IAI9D,OAAO,KAAK,WAAW,GAEtB;IAEQ,gBAAgB;IAIhB,iBAAiB,IAAI,IAAI;IAMzB,oBAAoB,IAAI,IAAI;IAK5B,OAAO,IAAI,IAAI;IAQxB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,mBAAmB,CAEzB;IAEF,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,qBAAqB;IA+B7B,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,wBAAwB;IAgBhC,OAAO,CAAC,wBAAwB;IAqChC,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,aAAa;IA6BZ,MAAM;CAsDhB"}
|
|
@@ -14,19 +14,20 @@
|
|
|
14
14
|
* - localStorage persistence of geometry
|
|
15
15
|
* - Viewport clamping on window resize
|
|
16
16
|
*/
|
|
17
|
+
import { panelShell } from '@syntro/design-system';
|
|
17
18
|
import { html, LitElement, nothing } from 'lit';
|
|
18
19
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
19
20
|
// =============================================================================
|
|
20
21
|
// Constants
|
|
21
22
|
// =============================================================================
|
|
22
23
|
const STORAGE_KEY = 'syntro:editor-panel';
|
|
23
|
-
const MIN_WIDTH =
|
|
24
|
-
const MIN_HEIGHT =
|
|
25
|
-
const FAB_SIZE =
|
|
26
|
-
const FAB_INSET =
|
|
27
|
-
const SNAP_THRESHOLD =
|
|
28
|
-
const DRAG_THRESHOLD =
|
|
29
|
-
const HANDLE_SIZE =
|
|
24
|
+
const MIN_WIDTH = panelShell.behavior.minWidth;
|
|
25
|
+
const MIN_HEIGHT = panelShell.behavior.minHeight;
|
|
26
|
+
const FAB_SIZE = panelShell.fab.size;
|
|
27
|
+
const FAB_INSET = panelShell.fab.inset;
|
|
28
|
+
const SNAP_THRESHOLD = panelShell.behavior.snapThreshold;
|
|
29
|
+
const DRAG_THRESHOLD = panelShell.behavior.dragThreshold;
|
|
30
|
+
const HANDLE_SIZE = panelShell.behavior.handleSize;
|
|
30
31
|
// =============================================================================
|
|
31
32
|
// Geometry Helpers
|
|
32
33
|
// =============================================================================
|
|
@@ -411,12 +412,14 @@ export class EditorPanelShellLit extends LitElement {
|
|
|
411
412
|
// Re-enable pointer events — parent editor container uses pointer-events:none
|
|
412
413
|
// so clicks pass through to elements beneath (e.g., the launcher FAB).
|
|
413
414
|
pointerEvents: 'auto',
|
|
414
|
-
background:
|
|
415
|
+
background: panelShell.background,
|
|
416
|
+
backdropFilter: `blur(${panelShell.backdropBlur})`,
|
|
417
|
+
webkitBackdropFilter: `blur(${panelShell.backdropBlur})`,
|
|
415
418
|
boxShadow: isDocked
|
|
416
419
|
? geo.docked === 'right'
|
|
417
|
-
?
|
|
418
|
-
:
|
|
419
|
-
:
|
|
420
|
+
? panelShell.shadows.dockedRight
|
|
421
|
+
: panelShell.shadows.dockedLeft
|
|
422
|
+
: panelShell.shadows.floating,
|
|
420
423
|
overflowY: 'auto',
|
|
421
424
|
overscrollBehavior: 'contain',
|
|
422
425
|
};
|
|
@@ -452,16 +455,14 @@ export class EditorPanelShellLit extends LitElement {
|
|
|
452
455
|
width: `${FAB_SIZE}px`,
|
|
453
456
|
height: `${FAB_SIZE}px`,
|
|
454
457
|
borderRadius: '50%',
|
|
455
|
-
border:
|
|
458
|
+
border: panelShell.fab.border,
|
|
456
459
|
cursor: 'grab',
|
|
457
460
|
display: 'flex',
|
|
458
461
|
alignItems: 'center',
|
|
459
462
|
justifyContent: 'center',
|
|
460
|
-
background:
|
|
461
|
-
color:
|
|
462
|
-
boxShadow: this.isOpen
|
|
463
|
-
? '0 4px 24px rgba(0,0,0,0.6), 0 0 0 2px rgba(255,255,255,0.08)'
|
|
464
|
-
: '0 4px 24px rgba(0,0,0,0.6)',
|
|
463
|
+
background: panelShell.fab.background,
|
|
464
|
+
color: panelShell.fab.color,
|
|
465
|
+
boxShadow: this.isOpen ? panelShell.fab.shadowOpen : panelShell.fab.shadowClosed,
|
|
465
466
|
transition: 'box-shadow 150ms ease',
|
|
466
467
|
touchAction: 'none',
|
|
467
468
|
userSelect: 'none',
|
|
@@ -85,6 +85,6 @@ export declare class ElementHighlightLit extends LitElement {
|
|
|
85
85
|
private _handleClick;
|
|
86
86
|
private _handleKeyDown;
|
|
87
87
|
private _handleRemove;
|
|
88
|
-
render(): import("lit-html").TemplateResult<1
|
|
88
|
+
render(): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
89
89
|
}
|
|
90
90
|
//# sourceMappingURL=ElementHighlightLit.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanelShellController — Lit ReactiveController for draggable/resizable panels.
|
|
3
|
+
*
|
|
4
|
+
* Owns geometry state (position, size, dock), pointer-event handlers for the
|
|
5
|
+
* drag FAB and 8 resize handles, localStorage persistence, and viewport
|
|
6
|
+
* clamping. A consuming LitElement calls `attachFab(el)` / `attachHandle(el, dir)`
|
|
7
|
+
* on its rendered DOM to hook up pointer events, and reads `panelStyles` /
|
|
8
|
+
* `fabStyles` to position the panel + FAB.
|
|
9
|
+
*
|
|
10
|
+
* Why a controller, not a wrapper element: `<slot>`-based wrapper elements
|
|
11
|
+
* don't compose well in light DOM (Lit wipes children on render), and shadow
|
|
12
|
+
* DOM breaks Playwright child selectors used by E2E tests. A ReactiveController
|
|
13
|
+
* is the Lit-idiomatic way to share stateful logic across elements.
|
|
14
|
+
*
|
|
15
|
+
* Emits one callback: `onToggle` when the FAB is pressed without dragging.
|
|
16
|
+
*/
|
|
17
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
18
|
+
export interface PanelGeometry {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
docked: 'left' | 'right' | null;
|
|
24
|
+
}
|
|
25
|
+
export type ResizeDir = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';
|
|
26
|
+
export interface PanelShellControllerOptions {
|
|
27
|
+
/** Unique storage key for geometry persistence. */
|
|
28
|
+
storageKey?: string;
|
|
29
|
+
/** Minimum width when resizing or undocked. Default 480. */
|
|
30
|
+
minWidth?: number;
|
|
31
|
+
/** Minimum height when resizing or undocked. Default 400. */
|
|
32
|
+
minHeight?: number;
|
|
33
|
+
/** Pixels the FAB must move before a press is treated as a drag. Default 5. */
|
|
34
|
+
dragThreshold?: number;
|
|
35
|
+
/** Edge-snap distance for docking. Default 20. */
|
|
36
|
+
snapThreshold?: number;
|
|
37
|
+
/** Size of the FAB in pixels. Default 56. */
|
|
38
|
+
fabSize?: number;
|
|
39
|
+
/** Inset of the FAB from the panel's top-left corner. Default 12. */
|
|
40
|
+
fabInset?: number;
|
|
41
|
+
/** Default geometry on first load when nothing is in storage. */
|
|
42
|
+
defaultGeometry?: Partial<PanelGeometry>;
|
|
43
|
+
/** Called when the FAB is pressed without dragging. */
|
|
44
|
+
onToggle?: () => void;
|
|
45
|
+
}
|
|
46
|
+
export interface ResizeHandleDef {
|
|
47
|
+
dir: ResizeDir;
|
|
48
|
+
style: Record<string, string>;
|
|
49
|
+
cursor: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function makeResizeHandles(): ResizeHandleDef[];
|
|
52
|
+
export declare class PanelShellController implements ReactiveController {
|
|
53
|
+
private readonly host;
|
|
54
|
+
private readonly opts;
|
|
55
|
+
private _geometry;
|
|
56
|
+
private _panelEl;
|
|
57
|
+
private _fabEl;
|
|
58
|
+
private _isDragging;
|
|
59
|
+
private _hasMoved;
|
|
60
|
+
private _dragOffset;
|
|
61
|
+
private _startPanelPos;
|
|
62
|
+
private _pendingGeo;
|
|
63
|
+
private _activeResize;
|
|
64
|
+
private _resizeStartMouse;
|
|
65
|
+
private _resizeStartGeo;
|
|
66
|
+
constructor(host: ReactiveControllerHost, options?: PanelShellControllerOptions);
|
|
67
|
+
/** Current geometry (read-only snapshot). */
|
|
68
|
+
get geometry(): PanelGeometry;
|
|
69
|
+
/** Pixel rect of the panel, resolving docked positions. */
|
|
70
|
+
get rect(): {
|
|
71
|
+
top: number;
|
|
72
|
+
left: number;
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
};
|
|
76
|
+
/** Pixel right edge of the panel (useful for positioning adjacent UI). */
|
|
77
|
+
get rightEdge(): number;
|
|
78
|
+
/** Set geometry directly — useful for testing or external state sync. */
|
|
79
|
+
setGeometry(geo: PanelGeometry): void;
|
|
80
|
+
/** Reset geometry to default (and clear persisted state). */
|
|
81
|
+
reset(): void;
|
|
82
|
+
/** Styles to apply to the panel element. */
|
|
83
|
+
panelStyles(): Record<string, string>;
|
|
84
|
+
/** Styles to apply to the FAB button. `isOpen` adjusts the shadow. */
|
|
85
|
+
fabStyles(isOpen: boolean): Record<string, string>;
|
|
86
|
+
/** Direction descriptors for the 8 resize handles. */
|
|
87
|
+
get handles(): ResizeHandleDef[];
|
|
88
|
+
/** Bind the current panel element so the controller can direct-write styles during drag/resize. */
|
|
89
|
+
attachPanel(el: HTMLElement | null): void;
|
|
90
|
+
/** Bind the current FAB element. */
|
|
91
|
+
attachFab(el: HTMLElement | null): void;
|
|
92
|
+
hostConnected(): void;
|
|
93
|
+
hostDisconnected(): void;
|
|
94
|
+
handleFabPointerDown: (e: PointerEvent) => void;
|
|
95
|
+
handleFabPointerMove: (e: PointerEvent) => void;
|
|
96
|
+
handleFabPointerUp: (e: PointerEvent) => void;
|
|
97
|
+
handleResizePointerDown: (dir: ResizeDir, e: PointerEvent) => void;
|
|
98
|
+
handleResizePointerMove: (e: PointerEvent) => void;
|
|
99
|
+
handleResizePointerUp: (e: PointerEvent) => void;
|
|
100
|
+
private _commit;
|
|
101
|
+
private _writeStylesDuringGesture;
|
|
102
|
+
private _onWindowResize;
|
|
103
|
+
private _clampToViewport;
|
|
104
|
+
private _undockGeometry;
|
|
105
|
+
private _panelRect;
|
|
106
|
+
private _loadGeometry;
|
|
107
|
+
private _saveGeometry;
|
|
108
|
+
private _resolveDefaultGeometry;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=PanelShellController.d.ts.map
|
package/node_modules/@syntrologie/shared-editor-ui/dist/controllers/PanelShellController.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PanelShellController.d.ts","sourceRoot":"","sources":["../../src/controllers/PanelShellController.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanelShellController — Lit ReactiveController for draggable/resizable panels.
|
|
3
|
+
*
|
|
4
|
+
* Owns geometry state (position, size, dock), pointer-event handlers for the
|
|
5
|
+
* drag FAB and 8 resize handles, localStorage persistence, and viewport
|
|
6
|
+
* clamping. A consuming LitElement calls `attachFab(el)` / `attachHandle(el, dir)`
|
|
7
|
+
* on its rendered DOM to hook up pointer events, and reads `panelStyles` /
|
|
8
|
+
* `fabStyles` to position the panel + FAB.
|
|
9
|
+
*
|
|
10
|
+
* Why a controller, not a wrapper element: `<slot>`-based wrapper elements
|
|
11
|
+
* don't compose well in light DOM (Lit wipes children on render), and shadow
|
|
12
|
+
* DOM breaks Playwright child selectors used by E2E tests. A ReactiveController
|
|
13
|
+
* is the Lit-idiomatic way to share stateful logic across elements.
|
|
14
|
+
*
|
|
15
|
+
* Emits one callback: `onToggle` when the FAB is pressed without dragging.
|
|
16
|
+
*/
|
|
17
|
+
import { panelShell } from '@syntro/design-system';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Constants
|
|
20
|
+
// =============================================================================
|
|
21
|
+
const DEFAULTS = {
|
|
22
|
+
minWidth: panelShell.behavior.minWidth,
|
|
23
|
+
minHeight: panelShell.behavior.minHeight,
|
|
24
|
+
dragThreshold: panelShell.behavior.dragThreshold,
|
|
25
|
+
snapThreshold: panelShell.behavior.snapThreshold,
|
|
26
|
+
fabSize: panelShell.fab.size,
|
|
27
|
+
fabInset: panelShell.fab.inset,
|
|
28
|
+
storageKey: 'syntro:editor-panel',
|
|
29
|
+
};
|
|
30
|
+
const HANDLE_SIZE = panelShell.behavior.handleSize;
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Helpers
|
|
33
|
+
// =============================================================================
|
|
34
|
+
export function makeResizeHandles() {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
dir: 'n',
|
|
38
|
+
style: {
|
|
39
|
+
top: '0',
|
|
40
|
+
left: `${HANDLE_SIZE}px`,
|
|
41
|
+
right: `${HANDLE_SIZE}px`,
|
|
42
|
+
height: `${HANDLE_SIZE}px`,
|
|
43
|
+
},
|
|
44
|
+
cursor: 'ns-resize',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
dir: 's',
|
|
48
|
+
style: {
|
|
49
|
+
bottom: '0',
|
|
50
|
+
left: `${HANDLE_SIZE}px`,
|
|
51
|
+
right: `${HANDLE_SIZE}px`,
|
|
52
|
+
height: `${HANDLE_SIZE}px`,
|
|
53
|
+
},
|
|
54
|
+
cursor: 'ns-resize',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
dir: 'e',
|
|
58
|
+
style: {
|
|
59
|
+
right: '0',
|
|
60
|
+
top: `${HANDLE_SIZE}px`,
|
|
61
|
+
bottom: `${HANDLE_SIZE}px`,
|
|
62
|
+
width: `${HANDLE_SIZE}px`,
|
|
63
|
+
},
|
|
64
|
+
cursor: 'ew-resize',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
dir: 'w',
|
|
68
|
+
style: {
|
|
69
|
+
left: '0',
|
|
70
|
+
top: `${HANDLE_SIZE}px`,
|
|
71
|
+
bottom: `${HANDLE_SIZE}px`,
|
|
72
|
+
width: `${HANDLE_SIZE}px`,
|
|
73
|
+
},
|
|
74
|
+
cursor: 'ew-resize',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
dir: 'ne',
|
|
78
|
+
style: {
|
|
79
|
+
top: '0',
|
|
80
|
+
right: '0',
|
|
81
|
+
width: `${HANDLE_SIZE * 2}px`,
|
|
82
|
+
height: `${HANDLE_SIZE * 2}px`,
|
|
83
|
+
},
|
|
84
|
+
cursor: 'nesw-resize',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
dir: 'nw',
|
|
88
|
+
style: {
|
|
89
|
+
top: '0',
|
|
90
|
+
left: '0',
|
|
91
|
+
width: `${HANDLE_SIZE * 2}px`,
|
|
92
|
+
height: `${HANDLE_SIZE * 2}px`,
|
|
93
|
+
},
|
|
94
|
+
cursor: 'nwse-resize',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
dir: 'se',
|
|
98
|
+
style: {
|
|
99
|
+
bottom: '0',
|
|
100
|
+
right: '0',
|
|
101
|
+
width: `${HANDLE_SIZE * 2}px`,
|
|
102
|
+
height: `${HANDLE_SIZE * 2}px`,
|
|
103
|
+
},
|
|
104
|
+
cursor: 'nwse-resize',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
dir: 'sw',
|
|
108
|
+
style: {
|
|
109
|
+
bottom: '0',
|
|
110
|
+
left: '0',
|
|
111
|
+
width: `${HANDLE_SIZE * 2}px`,
|
|
112
|
+
height: `${HANDLE_SIZE * 2}px`,
|
|
113
|
+
},
|
|
114
|
+
cursor: 'nesw-resize',
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Controller
|
|
120
|
+
// =============================================================================
|
|
121
|
+
export class PanelShellController {
|
|
122
|
+
constructor(host, options = {}) {
|
|
123
|
+
this._panelEl = null;
|
|
124
|
+
this._fabEl = null;
|
|
125
|
+
// Drag state
|
|
126
|
+
this._isDragging = false;
|
|
127
|
+
this._hasMoved = false;
|
|
128
|
+
this._dragOffset = { x: 0, y: 0 };
|
|
129
|
+
this._startPanelPos = { x: 0, y: 0 };
|
|
130
|
+
this._pendingGeo = null;
|
|
131
|
+
// Resize state
|
|
132
|
+
this._activeResize = null;
|
|
133
|
+
this._resizeStartMouse = { x: 0, y: 0 };
|
|
134
|
+
// ---- FAB pointer handlers ----
|
|
135
|
+
this.handleFabPointerDown = (e) => {
|
|
136
|
+
this._isDragging = true;
|
|
137
|
+
this._hasMoved = false;
|
|
138
|
+
const rect = this.rect;
|
|
139
|
+
this._startPanelPos = { x: rect.left, y: rect.top };
|
|
140
|
+
this._dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
141
|
+
try {
|
|
142
|
+
e.target.setPointerCapture(e.pointerId);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// best-effort
|
|
146
|
+
}
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
};
|
|
149
|
+
this.handleFabPointerMove = (e) => {
|
|
150
|
+
if (!this._isDragging)
|
|
151
|
+
return;
|
|
152
|
+
const newX = e.clientX - this._dragOffset.x;
|
|
153
|
+
const newY = e.clientY - this._dragOffset.y;
|
|
154
|
+
if (!this._hasMoved) {
|
|
155
|
+
const dx = Math.abs(newX - this._startPanelPos.x);
|
|
156
|
+
const dy = Math.abs(newY - this._startPanelPos.y);
|
|
157
|
+
if (dx < this.opts.dragThreshold && dy < this.opts.dragThreshold)
|
|
158
|
+
return;
|
|
159
|
+
this._hasMoved = true;
|
|
160
|
+
}
|
|
161
|
+
const geo = this._geometry;
|
|
162
|
+
const height = geo.docked ? window.innerHeight : geo.height;
|
|
163
|
+
this._writeStylesDuringGesture({ x: newX, y: newY, width: geo.width, height });
|
|
164
|
+
this._pendingGeo = { x: newX, y: newY, width: geo.width, height, docked: null };
|
|
165
|
+
};
|
|
166
|
+
this.handleFabPointerUp = (e) => {
|
|
167
|
+
try {
|
|
168
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// best-effort
|
|
172
|
+
}
|
|
173
|
+
const wasDragging = this._hasMoved;
|
|
174
|
+
this._isDragging = false;
|
|
175
|
+
this._hasMoved = false;
|
|
176
|
+
if (!wasDragging) {
|
|
177
|
+
this.opts.onToggle?.();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (this._pendingGeo) {
|
|
181
|
+
const geo = this._pendingGeo;
|
|
182
|
+
if (geo.x <= this.opts.snapThreshold) {
|
|
183
|
+
this._commit({ ...geo, docked: 'left', x: 0, y: 0 });
|
|
184
|
+
}
|
|
185
|
+
else if (geo.x + geo.width >= window.innerWidth - this.opts.snapThreshold) {
|
|
186
|
+
this._commit({ ...geo, docked: 'right', x: 0, y: 0 });
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this._commit(geo);
|
|
190
|
+
}
|
|
191
|
+
this._pendingGeo = null;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
// ---- Resize pointer handlers ----
|
|
195
|
+
this.handleResizePointerDown = (dir, e) => {
|
|
196
|
+
let geo = this._geometry;
|
|
197
|
+
if (geo.docked) {
|
|
198
|
+
geo = this._undockGeometry(geo);
|
|
199
|
+
this._commit(geo);
|
|
200
|
+
}
|
|
201
|
+
this._activeResize = dir;
|
|
202
|
+
this._resizeStartMouse = { x: e.clientX, y: e.clientY };
|
|
203
|
+
this._resizeStartGeo = { ...geo };
|
|
204
|
+
try {
|
|
205
|
+
e.target.setPointerCapture(e.pointerId);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// best-effort
|
|
209
|
+
}
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
};
|
|
212
|
+
this.handleResizePointerMove = (e) => {
|
|
213
|
+
if (!this._activeResize)
|
|
214
|
+
return;
|
|
215
|
+
const dx = e.clientX - this._resizeStartMouse.x;
|
|
216
|
+
const dy = e.clientY - this._resizeStartMouse.y;
|
|
217
|
+
const dir = this._activeResize;
|
|
218
|
+
const start = this._resizeStartGeo;
|
|
219
|
+
let { x, y, width, height } = start;
|
|
220
|
+
if (dir.includes('e'))
|
|
221
|
+
width = Math.max(this.opts.minWidth, start.width + dx);
|
|
222
|
+
if (dir.includes('w')) {
|
|
223
|
+
const newW = Math.max(this.opts.minWidth, start.width - dx);
|
|
224
|
+
x = start.x + start.width - newW;
|
|
225
|
+
width = newW;
|
|
226
|
+
}
|
|
227
|
+
if (dir.includes('s'))
|
|
228
|
+
height = Math.max(this.opts.minHeight, start.height + dy);
|
|
229
|
+
if (dir.includes('n')) {
|
|
230
|
+
const newH = Math.max(this.opts.minHeight, start.height - dy);
|
|
231
|
+
y = start.y + start.height - newH;
|
|
232
|
+
height = newH;
|
|
233
|
+
}
|
|
234
|
+
this._writeStylesDuringGesture({ x, y, width, height });
|
|
235
|
+
this._pendingGeo = { x, y, width, height, docked: null };
|
|
236
|
+
};
|
|
237
|
+
this.handleResizePointerUp = (e) => {
|
|
238
|
+
try {
|
|
239
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// best-effort
|
|
243
|
+
}
|
|
244
|
+
this._activeResize = null;
|
|
245
|
+
if (this._pendingGeo) {
|
|
246
|
+
this._commit(this._pendingGeo);
|
|
247
|
+
this._pendingGeo = null;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
this._onWindowResize = () => {
|
|
251
|
+
this._commit(this._clampToViewport(this._geometry));
|
|
252
|
+
};
|
|
253
|
+
this.host = host;
|
|
254
|
+
this.opts = {
|
|
255
|
+
storageKey: options.storageKey ?? DEFAULTS.storageKey,
|
|
256
|
+
minWidth: options.minWidth ?? DEFAULTS.minWidth,
|
|
257
|
+
minHeight: options.minHeight ?? DEFAULTS.minHeight,
|
|
258
|
+
dragThreshold: options.dragThreshold ?? DEFAULTS.dragThreshold,
|
|
259
|
+
snapThreshold: options.snapThreshold ?? DEFAULTS.snapThreshold,
|
|
260
|
+
fabSize: options.fabSize ?? DEFAULTS.fabSize,
|
|
261
|
+
fabInset: options.fabInset ?? DEFAULTS.fabInset,
|
|
262
|
+
onToggle: options.onToggle,
|
|
263
|
+
defaultGeometry: this._resolveDefaultGeometry(options.defaultGeometry),
|
|
264
|
+
};
|
|
265
|
+
this._geometry = this.opts.defaultGeometry;
|
|
266
|
+
this._resizeStartGeo = this._geometry;
|
|
267
|
+
host.addController(this);
|
|
268
|
+
}
|
|
269
|
+
// ---- Public API ----
|
|
270
|
+
/** Current geometry (read-only snapshot). */
|
|
271
|
+
get geometry() {
|
|
272
|
+
return this._geometry;
|
|
273
|
+
}
|
|
274
|
+
/** Pixel rect of the panel, resolving docked positions. */
|
|
275
|
+
get rect() {
|
|
276
|
+
return this._panelRect(this._geometry);
|
|
277
|
+
}
|
|
278
|
+
/** Pixel right edge of the panel (useful for positioning adjacent UI). */
|
|
279
|
+
get rightEdge() {
|
|
280
|
+
const r = this.rect;
|
|
281
|
+
return r.left + r.width;
|
|
282
|
+
}
|
|
283
|
+
/** Set geometry directly — useful for testing or external state sync. */
|
|
284
|
+
setGeometry(geo) {
|
|
285
|
+
this._geometry = geo;
|
|
286
|
+
this._saveGeometry(geo);
|
|
287
|
+
this.host.requestUpdate();
|
|
288
|
+
}
|
|
289
|
+
/** Reset geometry to default (and clear persisted state). */
|
|
290
|
+
reset() {
|
|
291
|
+
this._geometry = this.opts.defaultGeometry;
|
|
292
|
+
try {
|
|
293
|
+
localStorage.removeItem(this.opts.storageKey);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// ignore
|
|
297
|
+
}
|
|
298
|
+
this.host.requestUpdate();
|
|
299
|
+
}
|
|
300
|
+
/** Styles to apply to the panel element. */
|
|
301
|
+
panelStyles() {
|
|
302
|
+
const geo = this._geometry;
|
|
303
|
+
const blur = `blur(${panelShell.backdropBlur})`;
|
|
304
|
+
const shadow = geo.docked === 'right'
|
|
305
|
+
? panelShell.shadows.dockedRight
|
|
306
|
+
: geo.docked === 'left'
|
|
307
|
+
? panelShell.shadows.dockedLeft
|
|
308
|
+
: panelShell.shadows.floating;
|
|
309
|
+
const styles = {
|
|
310
|
+
position: 'fixed',
|
|
311
|
+
pointerEvents: 'auto',
|
|
312
|
+
background: panelShell.background,
|
|
313
|
+
backdropFilter: blur,
|
|
314
|
+
webkitBackdropFilter: blur,
|
|
315
|
+
boxShadow: shadow,
|
|
316
|
+
overflow: 'hidden',
|
|
317
|
+
};
|
|
318
|
+
if (geo.docked === 'left') {
|
|
319
|
+
styles.top = '0';
|
|
320
|
+
styles.left = '0';
|
|
321
|
+
styles.width = `${geo.width}px`;
|
|
322
|
+
styles.height = '100vh';
|
|
323
|
+
}
|
|
324
|
+
else if (geo.docked === 'right') {
|
|
325
|
+
styles.top = '0';
|
|
326
|
+
styles.right = '0';
|
|
327
|
+
styles.width = `${geo.width}px`;
|
|
328
|
+
styles.height = '100vh';
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
styles.top = `${geo.y}px`;
|
|
332
|
+
styles.left = `${geo.x}px`;
|
|
333
|
+
styles.width = `${geo.width}px`;
|
|
334
|
+
styles.height = `${geo.height}px`;
|
|
335
|
+
}
|
|
336
|
+
return styles;
|
|
337
|
+
}
|
|
338
|
+
/** Styles to apply to the FAB button. `isOpen` adjusts the shadow. */
|
|
339
|
+
fabStyles(isOpen) {
|
|
340
|
+
const rect = this.rect;
|
|
341
|
+
return {
|
|
342
|
+
position: 'fixed',
|
|
343
|
+
left: `${rect.left + this.opts.fabInset}px`,
|
|
344
|
+
top: `${rect.top + this.opts.fabInset}px`,
|
|
345
|
+
pointerEvents: 'auto',
|
|
346
|
+
width: `${this.opts.fabSize}px`,
|
|
347
|
+
height: `${this.opts.fabSize}px`,
|
|
348
|
+
borderRadius: '50%',
|
|
349
|
+
border: panelShell.fab.border,
|
|
350
|
+
cursor: 'grab',
|
|
351
|
+
display: 'flex',
|
|
352
|
+
alignItems: 'center',
|
|
353
|
+
justifyContent: 'center',
|
|
354
|
+
background: panelShell.fab.background,
|
|
355
|
+
color: panelShell.fab.color,
|
|
356
|
+
boxShadow: isOpen ? panelShell.fab.shadowOpen : panelShell.fab.shadowClosed,
|
|
357
|
+
transition: 'box-shadow 150ms ease',
|
|
358
|
+
touchAction: 'none',
|
|
359
|
+
userSelect: 'none',
|
|
360
|
+
webkitUserSelect: 'none',
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** Direction descriptors for the 8 resize handles. */
|
|
364
|
+
get handles() {
|
|
365
|
+
return makeResizeHandles();
|
|
366
|
+
}
|
|
367
|
+
/** Bind the current panel element so the controller can direct-write styles during drag/resize. */
|
|
368
|
+
attachPanel(el) {
|
|
369
|
+
this._panelEl = el;
|
|
370
|
+
}
|
|
371
|
+
/** Bind the current FAB element. */
|
|
372
|
+
attachFab(el) {
|
|
373
|
+
this._fabEl = el;
|
|
374
|
+
}
|
|
375
|
+
// ---- ReactiveController lifecycle ----
|
|
376
|
+
hostConnected() {
|
|
377
|
+
this._geometry = this._loadGeometry();
|
|
378
|
+
this.host.requestUpdate();
|
|
379
|
+
if (typeof window !== 'undefined') {
|
|
380
|
+
window.addEventListener('resize', this._onWindowResize);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
hostDisconnected() {
|
|
384
|
+
if (typeof window !== 'undefined') {
|
|
385
|
+
window.removeEventListener('resize', this._onWindowResize);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// ---- Internals ----
|
|
389
|
+
_commit(geo) {
|
|
390
|
+
this._geometry = geo;
|
|
391
|
+
this._saveGeometry(geo);
|
|
392
|
+
this.host.requestUpdate();
|
|
393
|
+
}
|
|
394
|
+
_writeStylesDuringGesture({ x, y, width, height, }) {
|
|
395
|
+
if (this._panelEl) {
|
|
396
|
+
this._panelEl.style.left = `${x}px`;
|
|
397
|
+
this._panelEl.style.top = `${y}px`;
|
|
398
|
+
this._panelEl.style.right = 'auto';
|
|
399
|
+
this._panelEl.style.width = `${width}px`;
|
|
400
|
+
this._panelEl.style.height = `${height}px`;
|
|
401
|
+
}
|
|
402
|
+
if (this._fabEl) {
|
|
403
|
+
this._fabEl.style.left = `${x + this.opts.fabInset}px`;
|
|
404
|
+
this._fabEl.style.top = `${y + this.opts.fabInset}px`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
_clampToViewport(geo) {
|
|
408
|
+
if (geo.docked)
|
|
409
|
+
return geo;
|
|
410
|
+
const vw = window.innerWidth;
|
|
411
|
+
const vh = window.innerHeight;
|
|
412
|
+
return {
|
|
413
|
+
...geo,
|
|
414
|
+
x: Math.max(-(geo.width - this.opts.fabSize), Math.min(geo.x, vw - this.opts.fabSize)),
|
|
415
|
+
y: Math.max(-this.opts.fabInset, Math.min(geo.y, vh - this.opts.fabSize - this.opts.fabInset)),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
_undockGeometry(geo) {
|
|
419
|
+
const r = this._panelRect(geo);
|
|
420
|
+
return { x: r.left, y: r.top, width: geo.width, height: r.height, docked: null };
|
|
421
|
+
}
|
|
422
|
+
_panelRect(geo) {
|
|
423
|
+
if (geo.docked === 'left') {
|
|
424
|
+
return { top: 0, left: 0, width: geo.width, height: window.innerHeight };
|
|
425
|
+
}
|
|
426
|
+
if (geo.docked === 'right') {
|
|
427
|
+
return {
|
|
428
|
+
top: 0,
|
|
429
|
+
left: window.innerWidth - geo.width,
|
|
430
|
+
width: geo.width,
|
|
431
|
+
height: window.innerHeight,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const { x: left, y: top, width, height } = geo;
|
|
435
|
+
return { top, left, width, height };
|
|
436
|
+
}
|
|
437
|
+
_loadGeometry() {
|
|
438
|
+
if (typeof window === 'undefined')
|
|
439
|
+
return this.opts.defaultGeometry;
|
|
440
|
+
try {
|
|
441
|
+
const raw = localStorage.getItem(this.opts.storageKey);
|
|
442
|
+
if (!raw)
|
|
443
|
+
return this.opts.defaultGeometry;
|
|
444
|
+
const p = JSON.parse(raw);
|
|
445
|
+
return {
|
|
446
|
+
x: typeof p.x === 'number' ? p.x : 0,
|
|
447
|
+
y: typeof p.y === 'number' ? p.y : 0,
|
|
448
|
+
width: Math.max(this.opts.minWidth, typeof p.width === 'number' ? p.width : this.opts.minWidth),
|
|
449
|
+
height: Math.max(this.opts.minHeight, typeof p.height === 'number' ? p.height : window.innerHeight),
|
|
450
|
+
docked: p.docked === 'left' || p.docked === 'right' ? p.docked : null,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return this.opts.defaultGeometry;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
_saveGeometry(geo) {
|
|
458
|
+
try {
|
|
459
|
+
localStorage.setItem(this.opts.storageKey, JSON.stringify(geo));
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// ignore
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
_resolveDefaultGeometry(partial) {
|
|
466
|
+
const w = typeof window !== 'undefined' ? window : null;
|
|
467
|
+
const base = {
|
|
468
|
+
x: 0,
|
|
469
|
+
y: 0,
|
|
470
|
+
width: DEFAULTS.minWidth,
|
|
471
|
+
height: w ? w.innerHeight : 800,
|
|
472
|
+
docked: 'right',
|
|
473
|
+
};
|
|
474
|
+
return { ...base, ...(partial ?? {}) };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
@@ -23,6 +23,8 @@ export { EmptyState } from './components/EmptyState';
|
|
|
23
23
|
export { GroupHeader } from './components/GroupHeader';
|
|
24
24
|
export type { TriggerJourneyProps } from './components/TriggerJourney';
|
|
25
25
|
export { TriggerJourney } from './components/TriggerJourney';
|
|
26
|
+
export type { PanelGeometry, PanelShellControllerOptions, ResizeDir, ResizeHandleDef, } from './controllers/PanelShellController';
|
|
27
|
+
export { makeResizeHandles, PanelShellController } from './controllers/PanelShellController';
|
|
26
28
|
export type { ConditionProgress, FormattedCondition } from './formatConditionLabel';
|
|
27
29
|
export { formatConditionLabel } from './formatConditionLabel';
|
|
28
30
|
export { useElementRect } from './hooks/useElementRect';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,2BAA2B,EAC3B,SAAS,EACT,eAAe,GAChB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC7F,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EACL,KAAK,UAAU,EACf,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC"}
|
|
@@ -18,6 +18,7 @@ export { ElementHighlight } from './components/ElementHighlight';
|
|
|
18
18
|
export { EmptyState } from './components/EmptyState';
|
|
19
19
|
export { GroupHeader } from './components/GroupHeader';
|
|
20
20
|
export { TriggerJourney } from './components/TriggerJourney';
|
|
21
|
+
export { makeResizeHandles, PanelShellController } from './controllers/PanelShellController';
|
|
21
22
|
export { formatConditionLabel } from './formatConditionLabel';
|
|
22
23
|
export { useElementRect } from './hooks/useElementRect';
|
|
23
24
|
export { useTriggerWhenStatus } from './hooks/useTriggerWhenStatus';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lit-elements.d.ts","sourceRoot":"","sources":["../src/lit-elements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,4BAA4B,CAAC;AACpC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AACxC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,4BAA4B,CAAC;AACpC,OAAO,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"lit-elements.d.ts","sourceRoot":"","sources":["../src/lit-elements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,8BAA8B,CAAC;AACtC,OAAO,4BAA4B,CAAC;AACpC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AACxC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,4BAA4B,CAAC;AACpC,OAAO,6BAA6B,CAAC"}
|
package/package.json
CHANGED