@adia-ai/web-modules 0.8.44 → 0.8.45
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/CHANGELOG.md +14 -0
- package/billing/billing-overview/billing-overview.class.js +7 -3
- package/billing/invoice-detail/invoice-detail.class.js +6 -3
- package/billing/invoice-history/invoice-history.class.js +6 -3
- package/billing/plan-picker/plan-picker.class.js +6 -3
- package/chat/chat-sidebar/chat-sidebar.js +26 -73
- package/dist/everything.min.js +110 -110
- package/dist/shell/admin-shell.min.css +1 -1
- package/dist/web-modules.min.css +1 -1
- package/dist/web-modules.sheet.js +1 -1
- package/editor/editor-sidebar/editor-sidebar.js +21 -13
- package/package.json +7 -6
- package/shared/sidebar-persist.js +152 -0
- package/shell/admin-shell/css/admin-shell.tokens.css +1 -1
- package/shell/admin-sidebar/admin-sidebar.js +26 -88
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared sidebar collapse/persist mechanics — admin-sidebar, chat-sidebar,
|
|
3
|
+
* and editor-sidebar each implemented their own copy of this (reactivity
|
|
4
|
+
* review R8b, gh#1767): collapse()/expand()/toggle(), the localStorage
|
|
5
|
+
* width persistence, and the storage-key resolution — byte-identical
|
|
6
|
+
* between admin-sidebar and chat-sidebar modulo their storage-key PREFIX.
|
|
7
|
+
*
|
|
8
|
+
* A `defineTrait()` (ADR-0018/0060) was considered and rejected for this
|
|
9
|
+
* extraction: traits are a PUBLIC, declaratively-attachable behavior
|
|
10
|
+
* catalog (`traits="name"` on any element, the MCP `get_traits` tool, the
|
|
11
|
+
* site trait-demo gallery). This mechanism needs a host that already
|
|
12
|
+
* owns `collapsed`/`name`/`minWidth` reactive properties — it isn't
|
|
13
|
+
* meaningfully attachable to an arbitrary element the way `pressable` or
|
|
14
|
+
* `resettable` are — and forking a full trait folder (demo page,
|
|
15
|
+
* sitemap/llms.txt registration, `_api-table` wiring) for a 2-consumer
|
|
16
|
+
* internal helper is disproportionate. A shared module still satisfies
|
|
17
|
+
* the actual cross-cutting-behavior concern the doctrine protects
|
|
18
|
+
* against (duplicated mechanics silently drifting apart) without minting
|
|
19
|
+
* public surface nothing else will ever declare.
|
|
20
|
+
*
|
|
21
|
+
* editor-sidebar is deliberately NOT a consumer of
|
|
22
|
+
* `attachSidebarCollapsePersist` below: it delegates width to an inner
|
|
23
|
+
* `<pane-ui>` and owns a GH-223 transition-suppression state machine
|
|
24
|
+
* tightly coupled to its own collapse()/expand() timing — folding that
|
|
25
|
+
* into this module's generic width math would risk exactly the
|
|
26
|
+
* regression class this extraction exists to avoid. It still imports
|
|
27
|
+
* `readSidebarWidth`/`persistSidebarWidth` for its own localStorage
|
|
28
|
+
* access, so all three sidebars share the actual persistence mechanics;
|
|
29
|
+
* only editor's collapse/expand METHODS stay bespoke to it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// px — at/below this width, a sidebar counts as "collapsed".
|
|
33
|
+
export const SIDEBAR_SNAP_THRESHOLD = 96;
|
|
34
|
+
// px — a settled expanded width below this (but above the collapse
|
|
35
|
+
// threshold) snaps up to it instead of resting mid-drag.
|
|
36
|
+
export const SIDEBAR_SNAP_MIN_USABLE = 160;
|
|
37
|
+
|
|
38
|
+
/** Read a persisted width string from localStorage. Swallows private-mode/quota errors. */
|
|
39
|
+
export function readSidebarWidth(key) {
|
|
40
|
+
try { return localStorage.getItem(key); } catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Persist a width string to localStorage. Swallows private-mode/quota errors. */
|
|
44
|
+
export function persistSidebarWidth(key, width) {
|
|
45
|
+
try { localStorage.setItem(key, width); } catch { /* private mode etc. */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Attach collapse()/expand()/toggle() + connect-time localStorage restore
|
|
50
|
+
* to a host that already declares reflected `collapsed`/`name`/`minWidth`
|
|
51
|
+
* properties and mutates its OWN `style.width` directly (admin-sidebar's
|
|
52
|
+
* and chat-sidebar's shape — never editor-sidebar's, see module doc above).
|
|
53
|
+
*
|
|
54
|
+
* @param {HTMLElement & {collapsed: boolean, name: string, minWidth: string}} host
|
|
55
|
+
* @param {{ prefix: string, guardZeroWidth?: boolean }} options
|
|
56
|
+
* `prefix` — this sidebar's OWN storage namespace, e.g. "adia-sidebar"
|
|
57
|
+
* (admin) or "adia-chat-sidebar" (chat) — gh#1767 keeps these distinct
|
|
58
|
+
* on purpose so no existing user's persisted width resets.
|
|
59
|
+
* `guardZeroWidth` — reproduces admin-sidebar's gh#286 SSR/zero-rect
|
|
60
|
+
* guard in `syncCollapsedFromWidth()`. chat-sidebar's pre-extraction
|
|
61
|
+
* behavior never had this guard; defaults to `false` to match it
|
|
62
|
+
* exactly (behavior-preserving — not a bug fix bundled into this
|
|
63
|
+
* extraction).
|
|
64
|
+
* @returns {{
|
|
65
|
+
* storageKey(): string,
|
|
66
|
+
* persist(width: string): void,
|
|
67
|
+
* restoreFromStorage(): void,
|
|
68
|
+
* syncCollapsedFromWidth(): void,
|
|
69
|
+
* collapse(): void,
|
|
70
|
+
* expand(): void,
|
|
71
|
+
* toggle(): boolean,
|
|
72
|
+
* }}
|
|
73
|
+
*/
|
|
74
|
+
export function attachSidebarCollapsePersist(host, { prefix, guardZeroWidth = false }) {
|
|
75
|
+
// The width the sidebar had before being collapsed — used for restore.
|
|
76
|
+
let previousExpandedWidth = '';
|
|
77
|
+
|
|
78
|
+
function storageKey() {
|
|
79
|
+
const id = host.name || host.getAttribute('slot') || 'default';
|
|
80
|
+
return `${prefix}-${id}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function persist(width) {
|
|
84
|
+
persistSidebarWidth(storageKey(), width);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function restoreFromStorage() {
|
|
88
|
+
const saved = readSidebarWidth(storageKey());
|
|
89
|
+
if (!saved) return;
|
|
90
|
+
host.style.width = saved;
|
|
91
|
+
// Only treat as "previous expanded" if actually expanded.
|
|
92
|
+
const w = parseFloat(saved);
|
|
93
|
+
if (!isNaN(w) && w > SIDEBAR_SNAP_THRESHOLD) {
|
|
94
|
+
previousExpandedWidth = saved;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function dispatchToggle(expanded) {
|
|
99
|
+
host.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
100
|
+
bubbles: true,
|
|
101
|
+
detail: { name: host.name || host.getAttribute('slot') || 'default', expanded },
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Collapse to the snap-floor width. Persists to localStorage. */
|
|
106
|
+
function collapse() {
|
|
107
|
+
if (host.collapsed) return;
|
|
108
|
+
const currentWidth = host.style.width || getComputedStyle(host).width;
|
|
109
|
+
if (parseFloat(currentWidth) > SIDEBAR_SNAP_THRESHOLD) {
|
|
110
|
+
previousExpandedWidth = currentWidth;
|
|
111
|
+
}
|
|
112
|
+
const minW = host.minWidth || getComputedStyle(host).minWidth;
|
|
113
|
+
host.style.width = minW;
|
|
114
|
+
persist(minW);
|
|
115
|
+
host.collapsed = true;
|
|
116
|
+
dispatchToggle(false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Restore to the previous expanded width (or default if none). */
|
|
120
|
+
function expand() {
|
|
121
|
+
if (!host.collapsed) return;
|
|
122
|
+
const restoreWidth = previousExpandedWidth || '';
|
|
123
|
+
host.style.width = restoreWidth;
|
|
124
|
+
persist(restoreWidth);
|
|
125
|
+
host.collapsed = false;
|
|
126
|
+
dispatchToggle(true);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Toggle collapsed state. Collapses if expanded, restores if collapsed. */
|
|
130
|
+
function toggle() {
|
|
131
|
+
if (host.collapsed) {
|
|
132
|
+
expand();
|
|
133
|
+
} else {
|
|
134
|
+
collapse();
|
|
135
|
+
}
|
|
136
|
+
return host.collapsed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// gh#286 — a zero rect means "unknown," not "collapsed": linkedom/SSR
|
|
140
|
+
// shims have no layout engine and always return 0×0, and a real
|
|
141
|
+
// browser can connect an element before its first layout pass (a
|
|
142
|
+
// `display:none` ancestor, e.g). Only admin-sidebar guards this today
|
|
143
|
+
// (guardZeroWidth) — chat-sidebar's pre-extraction behavior always
|
|
144
|
+
// derived `collapsed` from the raw measured width, guard or not.
|
|
145
|
+
function syncCollapsedFromWidth() {
|
|
146
|
+
const w = host.getBoundingClientRect().width;
|
|
147
|
+
if (w === 0 && guardZeroWidth) return;
|
|
148
|
+
host.collapsed = w <= SIDEBAR_SNAP_THRESHOLD;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { storageKey, persist, restoreFromStorage, syncCollapsedFromWidth, collapse, expand, toggle };
|
|
152
|
+
}
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
--nav-duration: var(--a-duration);
|
|
113
113
|
--nav-duration-fast: var(--a-duration-fast);
|
|
114
114
|
--nav-easing: var(--a-easing);
|
|
115
|
-
--nav-label-px: var(--a-space-
|
|
115
|
+
--nav-label-px: var(--a-space-4);
|
|
116
116
|
--nav-label-py: var(--a-space-1);
|
|
117
117
|
--nav-label-font-size: var(--a-kicker-sm);
|
|
118
118
|
--nav-label-weight: var(--a-weight-medium);
|
|
@@ -37,13 +37,22 @@
|
|
|
37
37
|
* Backwards compat: <admin-shell> still recognizes the legacy
|
|
38
38
|
* `<aside data-sidebar="leading">` shape via :is() selector. New
|
|
39
39
|
* code should prefer <admin-sidebar>.
|
|
40
|
+
*
|
|
41
|
+
* gh#1767 (reactivity review R8b) — collapse()/expand()/toggle() and the
|
|
42
|
+
* localStorage persistence now come from ../../shared/sidebar-persist.js,
|
|
43
|
+
* shared verbatim with <chat-sidebar> (the storage-key PREFIX is this
|
|
44
|
+
* component's own — `adia-sidebar-*` — so no existing user's persisted
|
|
45
|
+
* width resets). See that module's header for why editor-sidebar isn't a
|
|
46
|
+
* third consumer.
|
|
40
47
|
*/
|
|
41
48
|
|
|
42
49
|
import { defineIfFree } from '@adia-ai/web-components/core/register';
|
|
43
50
|
import { UIElement } from '@adia-ai/web-components/core/element';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
import {
|
|
52
|
+
attachSidebarCollapsePersist,
|
|
53
|
+
SIDEBAR_SNAP_THRESHOLD as SNAP_THRESHOLD,
|
|
54
|
+
SIDEBAR_SNAP_MIN_USABLE as SNAP_MIN_USABLE,
|
|
55
|
+
} from '../../shared/sidebar-persist.js';
|
|
47
56
|
|
|
48
57
|
class AdminSidebar extends UIElement {
|
|
49
58
|
static properties = {
|
|
@@ -61,17 +70,19 @@ class AdminSidebar extends UIElement {
|
|
|
61
70
|
// handle — one-shot per element so re-renders stay quiet. GC-friendly.
|
|
62
71
|
static #warnedNoHandle = new WeakSet();
|
|
63
72
|
|
|
64
|
-
// The width the sidebar had before being collapsed — used for restore.
|
|
65
|
-
// Map keyed by sidebar name allows multiple sidebars on one host.
|
|
66
|
-
#previousExpandedWidth = '';
|
|
67
73
|
#resizeCleanups = [];
|
|
68
74
|
#childRO = null;
|
|
75
|
+
// Collapse/expand/toggle/persist mechanics — shared with chat-sidebar
|
|
76
|
+
// (reactivity review R8b, gh#1767); see shared/sidebar-persist.js for
|
|
77
|
+
// why editor-sidebar isn't a third consumer. `guardZeroWidth: true`
|
|
78
|
+
// reproduces the gh#286 SSR/zero-rect guard admin-sidebar has always had.
|
|
79
|
+
#persist = attachSidebarCollapsePersist(this, { prefix: 'adia-sidebar', guardZeroWidth: true });
|
|
69
80
|
|
|
70
81
|
connected() {
|
|
71
|
-
this.#restoreFromStorage();
|
|
82
|
+
this.#persist.restoreFromStorage();
|
|
72
83
|
if (this.resizable) this.#setupResizeHandle();
|
|
73
84
|
this.#setupChildResizeObserver();
|
|
74
|
-
this.#syncCollapsedFromWidth();
|
|
85
|
+
this.#persist.syncCollapsedFromWidth();
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
disconnected() {
|
|
@@ -88,81 +99,17 @@ class AdminSidebar extends UIElement {
|
|
|
88
99
|
* Returns the new collapsed value.
|
|
89
100
|
*/
|
|
90
101
|
toggle() {
|
|
91
|
-
|
|
92
|
-
this.expand();
|
|
93
|
-
} else {
|
|
94
|
-
this.collapse();
|
|
95
|
-
}
|
|
96
|
-
return this.collapsed;
|
|
102
|
+
return this.#persist.toggle();
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/** Collapse to the snap-floor width. Persists to localStorage. */
|
|
100
106
|
collapse() {
|
|
101
|
-
|
|
102
|
-
// Remember current expanded width before collapsing
|
|
103
|
-
const currentWidth = this.style.width || getComputedStyle(this).width;
|
|
104
|
-
if (parseFloat(currentWidth) > SNAP_THRESHOLD) {
|
|
105
|
-
this.#previousExpandedWidth = currentWidth;
|
|
106
|
-
}
|
|
107
|
-
const minW = this.minWidth || getComputedStyle(this).minWidth;
|
|
108
|
-
this.style.width = minW;
|
|
109
|
-
this.#persist(minW);
|
|
110
|
-
this.collapsed = true;
|
|
111
|
-
this.#dispatchToggle(false);
|
|
107
|
+
this.#persist.collapse();
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
/** Restore to the previous expanded width (or default if none). */
|
|
115
111
|
expand() {
|
|
116
|
-
|
|
117
|
-
const restoreWidth = this.#previousExpandedWidth || '';
|
|
118
|
-
this.style.width = restoreWidth;
|
|
119
|
-
this.#persist(restoreWidth);
|
|
120
|
-
this.collapsed = false;
|
|
121
|
-
this.#dispatchToggle(true);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── Persistence ──
|
|
125
|
-
|
|
126
|
-
#storageKey() {
|
|
127
|
-
const id = this.name || this.getAttribute('slot') || 'default';
|
|
128
|
-
return `adia-sidebar-${id}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
#persist(width) {
|
|
132
|
-
try { localStorage.setItem(this.#storageKey(), width); } catch {}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
#restoreFromStorage() {
|
|
136
|
-
try {
|
|
137
|
-
const saved = localStorage.getItem(this.#storageKey());
|
|
138
|
-
if (!saved) return;
|
|
139
|
-
this.style.width = saved;
|
|
140
|
-
// Only treat as "previous expanded" if actually expanded
|
|
141
|
-
const w = parseFloat(saved);
|
|
142
|
-
if (!isNaN(w) && w > SNAP_THRESHOLD) {
|
|
143
|
-
this.#previousExpandedWidth = saved;
|
|
144
|
-
}
|
|
145
|
-
} catch {}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ── Reflect [collapsed] from current measured width ──
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* A zero rect means "unknown," not "collapsed" — linkedom/SSR shims
|
|
152
|
-
* have no layout engine and always return 0×0, and a real browser can
|
|
153
|
-
* connect an element before its first layout pass (a `display:none`
|
|
154
|
-
* ancestor, e.g.). Snapping collapsed on a bogus zero-width read used
|
|
155
|
-
* to require every SSR consumer to fake a viewport-sized rect just to
|
|
156
|
-
* keep the sidebar expanded server-side (gh#286). Deriving state from
|
|
157
|
-
* a real width stays synchronous; deriving it from a real ZERO is the
|
|
158
|
-
* bug — that path now leaves whatever state connected() already
|
|
159
|
-
* restored (persisted width, or the default) until the ResizeObserver
|
|
160
|
-
* below fires with genuine layout.
|
|
161
|
-
*/
|
|
162
|
-
#syncCollapsedFromWidth() {
|
|
163
|
-
const w = this.getBoundingClientRect().width;
|
|
164
|
-
if (w === 0) return;
|
|
165
|
-
this.collapsed = w <= SNAP_THRESHOLD;
|
|
112
|
+
this.#persist.expand();
|
|
166
113
|
}
|
|
167
114
|
|
|
168
115
|
// ── Resize drag handle ──
|
|
@@ -222,8 +169,8 @@ class AdminSidebar extends UIElement {
|
|
|
222
169
|
this.style.width = `${SNAP_MIN_USABLE}px`;
|
|
223
170
|
}
|
|
224
171
|
|
|
225
|
-
this.#persist(this.style.width);
|
|
226
|
-
this.#syncCollapsedFromWidth();
|
|
172
|
+
this.#persist.persist(this.style.width);
|
|
173
|
+
this.#persist.syncCollapsedFromWidth();
|
|
227
174
|
this.dispatchEvent(new CustomEvent('sidebar-resize', {
|
|
228
175
|
bubbles: true,
|
|
229
176
|
detail: { name: this.name || slot, width: this.getBoundingClientRect().width },
|
|
@@ -242,8 +189,8 @@ class AdminSidebar extends UIElement {
|
|
|
242
189
|
// is the deferred [collapsed] correction for gh#286: its first tick
|
|
243
190
|
// fires with genuine layout (SSR hydration's first client paint, or
|
|
244
191
|
// a browser connect that started inside display:none), so a
|
|
245
|
-
// zero-width connect() that left #syncCollapsedFromWidth
|
|
246
|
-
// gets resolved here instead of staying wrong forever. ──
|
|
192
|
+
// zero-width connect() that left #persist.syncCollapsedFromWidth()
|
|
193
|
+
// a no-op gets resolved here instead of staying wrong forever. ──
|
|
247
194
|
|
|
248
195
|
#setupChildResizeObserver() {
|
|
249
196
|
// gh#285 — SSR DOM shims (linkedom) have no ResizeObserver global.
|
|
@@ -259,15 +206,6 @@ class AdminSidebar extends UIElement {
|
|
|
259
206
|
});
|
|
260
207
|
this.#childRO.observe(this);
|
|
261
208
|
}
|
|
262
|
-
|
|
263
|
-
// ── Internal — dispatch the toggle event in a consistent shape ──
|
|
264
|
-
|
|
265
|
-
#dispatchToggle(expanded) {
|
|
266
|
-
this.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
267
|
-
bubbles: true,
|
|
268
|
-
detail: { name: this.name || this.getAttribute('slot') || 'default', expanded },
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
209
|
}
|
|
272
210
|
|
|
273
211
|
defineIfFree('admin-sidebar', AdminSidebar);
|