@lybx-leyw/dsh-pages 0.0.0-stage → 0.1.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/lib/client.js ADDED
@@ -0,0 +1,958 @@
1
+ window.__ModuleLoader__.load({id:"@lybx-leyw/dsh-pages",factory:(require)=>{var module={exports:{}};var exports=module.exports;
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/client.jsx
31
+ var client_exports = {};
32
+ __export(client_exports, {
33
+ apply: () => apply,
34
+ applyWithConfig: () => applyWithConfig,
35
+ inject: () => inject,
36
+ installPageShell: () => installPageShell,
37
+ name: () => name,
38
+ normalizeConfig: () => normalizeConfig
39
+ });
40
+ module.exports = __toCommonJS(client_exports);
41
+
42
+ // src/state.js
43
+ function isLocalizedText(value) {
44
+ return value !== null && typeof value === "object" && typeof value.en === "string" && Object.values(value).every((entry) => typeof entry === "string");
45
+ }
46
+ function normalizeConfig(input = {}) {
47
+ const text = (key, fallback) => {
48
+ const value = input[key] ?? fallback;
49
+ if (typeof value === "string" && value.length > 0 || isLocalizedText(value)) return value;
50
+ throw new Error(`dsh-pages: \`${key}\` must be a non-empty string or LocalizedText`);
51
+ };
52
+ const id = (key, fallback) => {
53
+ const value = input[key] ?? fallback;
54
+ if (typeof value !== "string" || value.length === 0) throw new Error(`dsh-pages: \`${key}\` must be a non-empty string`);
55
+ return value;
56
+ };
57
+ const homePage = id("homePage", "home");
58
+ const managerPage = id("managerPage", "pages");
59
+ const pageSlot = id("pageSlot", "page.content");
60
+ if (!pageSlot.startsWith("page.")) throw new Error("dsh-pages: `pageSlot` must start with page.");
61
+ if (homePage === managerPage) throw new Error("dsh-pages: `managerPage` must differ from `homePage`");
62
+ return {
63
+ homePage,
64
+ managerPage,
65
+ pageSlot,
66
+ classPrefix: id("classPrefix", "dsh-pages"),
67
+ navigationSlot: id("navigationSlot", "page.navigation"),
68
+ homeLabel: text("homeLabel", { en: "DSH workbench", zh: "DSH \u5DE5\u4F5C\u53F0" }),
69
+ managerLabel: text("managerLabel", { en: "Pages", zh: "\u9875\u9762\u7BA1\u7406" }),
70
+ homeIcon: input.homeIcon,
71
+ managerIcon: input.managerIcon
72
+ };
73
+ }
74
+ var PAGE_REGIONS = Object.freeze(["top", "overflow", "bottom"]);
75
+ var RAIL_WIDTH = Object.freeze({ min: 40, max: 96, default: 64 });
76
+ function validRailWidth(width) {
77
+ return Number.isInteger(width) && width >= RAIL_WIDTH.min && width <= RAIL_WIDTH.max;
78
+ }
79
+ function createPagesState(config, initialPage = config.homePage, { preferences } = {}) {
80
+ let snapshot = { railWidth: preferences?.getRailWidth?.() ?? RAIL_WIDTH.default, activePage: initialPage, pages: Object.freeze([]) };
81
+ const listeners = /* @__PURE__ */ new Set();
82
+ const pages = /* @__PURE__ */ new Map();
83
+ const registrations = /* @__PURE__ */ new Map();
84
+ let sequence = 0;
85
+ const publish = (patch) => {
86
+ if (Object.entries(patch).every(([key, value]) => snapshot[key] === value)) return;
87
+ snapshot = { ...snapshot, ...patch };
88
+ for (const listener of [...listeners]) listener();
89
+ };
90
+ const publishPages = (patch = {}) => publish({
91
+ ...patch,
92
+ pages: Object.freeze([...pages.values()].sort((a, b) => a.order - b.order || a.seq - b.seq).map((entry) => Object.freeze({ ...entry })))
93
+ });
94
+ const validateRegion = (region) => {
95
+ if (!PAGE_REGIONS.includes(region)) throw new Error(`dsh-pages: page \`region\` must be one of ${PAGE_REGIONS.join(", ")}`);
96
+ };
97
+ return {
98
+ getSnapshot: () => snapshot,
99
+ subscribe(listener) {
100
+ listeners.add(listener);
101
+ return () => listeners.delete(listener);
102
+ },
103
+ selectPage(activePage) {
104
+ if (!pages.has(activePage)) throw new Error(`dsh-pages: unknown page "${activePage}"`);
105
+ publish({ activePage });
106
+ },
107
+ setRailWidth(width) {
108
+ if (!validRailWidth(width)) throw new RangeError("dsh-pages: navigation width must be an integer between 40 and 96");
109
+ preferences?.setRailWidth?.(width);
110
+ publish({ railWidth: width });
111
+ },
112
+ /** The returned disposer removes only this live registration, never a later one. */
113
+ registerPage(spec) {
114
+ if (spec === null || typeof spec !== "object") throw new TypeError("dsh-pages: registerPage() needs a spec object");
115
+ const { id, label, icon } = spec;
116
+ if (typeof id !== "string" || !id) throw new Error("dsh-pages: page `id` must be a non-empty string");
117
+ if (!(typeof label === "string" && label.length > 0) && !isLocalizedText(label)) throw new Error("dsh-pages: page `label` must be a non-empty string or LocalizedText");
118
+ if (pages.has(id)) throw new Error(`dsh-pages: page "${id}" is already registered`);
119
+ const region = spec.region ?? "top";
120
+ validateRegion(region);
121
+ const order = spec.order ?? 0;
122
+ if (!Number.isFinite(order)) throw new Error("dsh-pages: page `order` must be a finite number");
123
+ if (icon !== void 0 && typeof icon !== "function" && (icon === null || typeof icon !== "object")) throw new Error("dsh-pages: page `icon` must be a component");
124
+ const entry = { id, label, icon, glyph: spec.glyph ?? "\u25CF", region, order, seq: ++sequence, ...preferences?.getArrangement(id) };
125
+ pages.set(id, entry);
126
+ registrations.set(id, entry);
127
+ publishPages();
128
+ let live = true;
129
+ return () => {
130
+ if (!live) return;
131
+ live = false;
132
+ if (registrations.get(id) !== entry) return;
133
+ pages.delete(id);
134
+ registrations.delete(id);
135
+ publishPages(snapshot.activePage === id ? { activePage: config.homePage } : {});
136
+ };
137
+ },
138
+ setPageRegion(id, region) {
139
+ validateRegion(region);
140
+ const entry = pages.get(id);
141
+ if (!entry) return false;
142
+ preferences?.setArrangement(id, { region });
143
+ if (entry.region === region) return false;
144
+ if (entry.pinned) preferences?.setArrangement(id, { pinned: false, pinOrder: 0 });
145
+ pages.set(id, { ...entry, region, pinned: false, pinOrder: 0 });
146
+ publishPages();
147
+ return true;
148
+ },
149
+ /** Append/remove a shortcut; do not change recycle membership or register a page. */
150
+ setPagePinned(id, pinned) {
151
+ const entry = pages.get(id);
152
+ if (!entry || entry.region !== "overflow" || Boolean(entry.pinned) === Boolean(pinned)) return false;
153
+ const pinOrder = pinned ? Math.max(0, ...[...pages.values()].map((page) => page.pinOrder || 0)) + 1 : 0;
154
+ const patch = { pinned: Boolean(pinned), pinOrder };
155
+ preferences?.setArrangement(id, { region: "overflow", ...patch });
156
+ pages.set(id, { ...entry, ...patch });
157
+ publishPages();
158
+ return true;
159
+ },
160
+ setPageOrder(id, order) {
161
+ if (!Number.isFinite(order)) throw new Error("dsh-pages: page `order` must be a finite number");
162
+ const entry = pages.get(id);
163
+ if (!entry) return false;
164
+ preferences?.setArrangement(id, { order });
165
+ if (entry.order === order) return false;
166
+ pages.set(id, { ...entry, order });
167
+ publishPages();
168
+ return true;
169
+ },
170
+ /** Move to the front of the default top group, distinct from pinning. */
171
+ promotePage(id) {
172
+ const entry = pages.get(id);
173
+ if (!entry) return false;
174
+ const others = [...pages.values()].filter((page) => page.region === "top" && page.id !== id);
175
+ const first = others.length ? Math.min(...others.map((page) => page.order)) : void 0;
176
+ if (entry.region === "top" && (first === void 0 || entry.order < first)) {
177
+ preferences?.setArrangement(id, { region: "top", order: entry.order });
178
+ return false;
179
+ }
180
+ const patch = { region: "top", order: first === void 0 ? 0 : first - 1, pinned: false, pinOrder: 0 };
181
+ pages.set(id, { ...entry, ...patch });
182
+ preferences?.setArrangement(id, patch);
183
+ publishPages();
184
+ return true;
185
+ }
186
+ };
187
+ }
188
+
189
+ // src/page-shell.jsx
190
+ var import_react2 = __toESM(require("react"), 1);
191
+
192
+ // src/preferences.js
193
+ var VERSION = 1;
194
+ var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
195
+ var empty = () => ({ version: VERSION, pages: /* @__PURE__ */ Object.create(null) });
196
+ function preferencesStorageKey(config) {
197
+ return "dsh-pages:preferences:v1:" + encodeURIComponent(JSON.stringify([
198
+ config.navigationSlot,
199
+ config.pageSlot,
200
+ config.classPrefix,
201
+ config.homePage
202
+ ]));
203
+ }
204
+ function decodePreferences(value) {
205
+ if (!isRecord(value) || value.version !== VERSION || !isRecord(value.pages)) throw new Error("unsupported preferences document");
206
+ const result = empty();
207
+ if (validRailWidth(value.railWidth)) result.railWidth = value.railWidth;
208
+ for (const [id, arrangement] of Object.entries(value.pages)) {
209
+ if (!id || !isRecord(arrangement)) continue;
210
+ const saved = {};
211
+ if (PAGE_REGIONS.includes(arrangement.region)) saved.region = arrangement.region;
212
+ if (Number.isFinite(arrangement.order)) saved.order = arrangement.order;
213
+ if (typeof arrangement.pinned === "boolean") saved.pinned = arrangement.pinned;
214
+ if (Number.isFinite(arrangement.pinOrder) && arrangement.pinOrder >= 0) saved.pinOrder = arrangement.pinOrder;
215
+ if (Object.keys(saved).length) result.pages[id] = saved;
216
+ }
217
+ return result;
218
+ }
219
+ function createBrowserPreferences(config, { getStorage = () => globalThis.localStorage, warn = (...args) => console.warn(...args) } = {}) {
220
+ const key = preferencesStorageKey(config);
221
+ let document2 = empty();
222
+ const report = (error) => warn("dsh-pages: browser preferences unavailable or invalid; continuing with in-memory settings.", error);
223
+ try {
224
+ const storage = getStorage();
225
+ if (!storage) throw new Error("localStorage is unavailable");
226
+ const raw = storage.getItem(key);
227
+ if (raw !== null && raw !== void 0) document2 = decodePreferences(JSON.parse(raw));
228
+ } catch (error) {
229
+ report(error);
230
+ }
231
+ const save = () => {
232
+ try {
233
+ const storage = getStorage();
234
+ if (!storage) throw new Error("localStorage is unavailable");
235
+ storage.setItem(key, JSON.stringify(document2));
236
+ } catch (error) {
237
+ report(error);
238
+ }
239
+ };
240
+ return {
241
+ key,
242
+ getRailWidth() {
243
+ return document2.railWidth ?? RAIL_WIDTH.default;
244
+ },
245
+ setRailWidth(width) {
246
+ if (!validRailWidth(width)) throw new RangeError("dsh-pages: navigation width must be an integer between 40 and 96");
247
+ document2.railWidth = width;
248
+ save();
249
+ },
250
+ getArrangement(id) {
251
+ return { ...document2.pages[id] };
252
+ },
253
+ setArrangement(id, patch) {
254
+ const next = { ...document2.pages[id], ...patch };
255
+ const valid = {};
256
+ if (PAGE_REGIONS.includes(next.region)) valid.region = next.region;
257
+ if (Number.isFinite(next.order)) valid.order = next.order;
258
+ if (typeof next.pinned === "boolean") valid.pinned = next.pinned;
259
+ if (Number.isFinite(next.pinOrder) && next.pinOrder >= 0) valid.pinOrder = next.pinOrder;
260
+ document2.pages[id] = valid;
261
+ save();
262
+ }
263
+ };
264
+ }
265
+
266
+ // src/pages-service.js
267
+ function createPages(input = {}, options = {}) {
268
+ const config = normalizeConfig(input);
269
+ const preferences = options.preferences ?? (typeof window !== "undefined" ? createBrowserPreferences(config) : void 0);
270
+ const state = createPagesState(config, config.homePage, { preferences });
271
+ const cleanups = [
272
+ state.registerPage({ id: config.homePage, label: config.homeLabel, icon: config.homeIcon, glyph: "\u2302", order: 0 }),
273
+ state.registerPage({ id: config.managerPage, label: config.managerLabel, icon: config.managerIcon, glyph: "\u25A6", order: 90, region: "bottom" })
274
+ ];
275
+ return {
276
+ state,
277
+ service: {
278
+ config,
279
+ getSnapshot: state.getSnapshot,
280
+ subscribe: state.subscribe,
281
+ selectPage: state.selectPage,
282
+ registerPage: state.registerPage,
283
+ setPageRegion: state.setPageRegion,
284
+ setPagePinned: state.setPagePinned,
285
+ setRailWidth: state.setRailWidth,
286
+ setPageOrder: state.setPageOrder,
287
+ promotePage: state.promotePage
288
+ },
289
+ dispose() {
290
+ for (const cleanup of cleanups.splice(0).reverse()) cleanup();
291
+ }
292
+ };
293
+ }
294
+
295
+ // src/page-manager.jsx
296
+ var import_react = __toESM(require("react"), 1);
297
+ var import_jsx_runtime = require("react/jsx-runtime");
298
+ var REGIONS = ["top", "overflow", "bottom"];
299
+ var DRAG_TYPE = "application/x-dsh-pages-manager";
300
+ var styles = `
301
+ [data-pages-manager]{box-sizing:border-box;width:100%;max-width:960px;min-width:0;margin:0 auto;padding:clamp(16px,4vw,40px);color:var(--dsw-alias-label-primary,#101318);font:inherit;overflow-wrap:anywhere}
302
+ [data-pages-manager] *{box-sizing:border-box;min-width:0}
303
+ [data-pages-manager] h2{margin:0;font-size:24px;letter-spacing:-.04em;font-weight:600}
304
+ [data-pages-manager] header p{margin:8px 0 28px;color:var(--dsw-alias-label-secondary,#6b7178);font-size:13px;line-height:1.6}
305
+ [data-pages-manager] section{margin:0 0 26px}
306
+ [data-pages-manager] h3{display:flex;align-items:center;gap:10px;margin:0 0 10px;font-size:11px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--dsw-alias-label-secondary,#6b7178)}
307
+ [data-pages-manager] h3 span{font:11px ui-monospace,monospace;opacity:.6}
308
+ [data-pages-manager] ul{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:6px;min-height:48px;border-radius:8px}
309
+ [data-pages-manager] li{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-settings-card-stroke,#00000014);border-radius:8px;background:var(--dsw-alias-settings-card-fill,#00000005)}
310
+ [data-pages-manager] li[draggable=true]{cursor:grab}
311
+ [data-pages-manager] li[data-dragging=true]{opacity:.4}
312
+ [data-pages-manager] li[data-insert=before]{box-shadow:inset 0 3px var(--dsw-alias-brand-primary,#2f6fed)}
313
+ [data-pages-manager] li[data-insert=after]{box-shadow:inset 0 -3px var(--dsw-alias-brand-primary,#2f6fed)}
314
+ [data-pages-manager] ul[data-drop=true]{outline:2px dashed var(--dsw-alias-brand-primary,#2f6fed);outline-offset:3px}
315
+ [data-pages-manager] .pm-identity{display:flex;align-items:center;gap:10px;flex:1 1 180px}
316
+ [data-pages-manager] .pm-grip{color:var(--dsw-alias-label-caption,#8a9099);user-select:none}
317
+ [data-pages-manager] .pm-icon{display:grid;place-items:center;width:22px;height:22px;flex:none}
318
+ [data-pages-manager] .pm-icon svg{width:20px;height:20px}
319
+ [data-pages-manager] .pm-name{font-size:13px;font-weight:500}
320
+ [data-pages-manager] .pm-id{display:block;margin-top:2px;font:10px ui-monospace,monospace;color:var(--dsw-alias-label-caption,#8a9099)}
321
+ [data-pages-manager] .pm-actions{display:flex;flex:0 1 auto;flex-wrap:wrap;align-items:center;gap:6px;max-width:100%}
322
+ [data-pages-manager] button,[data-pages-manager] select{min-height:32px;max-width:100%;border:1px solid var(--dsw-alias-settings-card-stroke,#0000001f);border-radius:5px;background:var(--dsw-alias-bg-base,#f4f5f7);color:var(--dsw-alias-label-secondary,#4b5058);font:inherit;font-size:12px;padding:4px 8px;cursor:pointer}
323
+ [data-pages-manager] button{min-width:32px;white-space:normal;overflow-wrap:anywhere}
324
+ [data-pages-manager] button:hover:not(:disabled){background:var(--dsw-alias-button-elevated-fill,#0000000f)}
325
+ [data-pages-manager] :is(button,select):focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f6fed);outline-offset:2px}
326
+ [data-pages-manager] button:disabled{opacity:.35;cursor:not-allowed}
327
+ [data-pages-manager] button[role=switch][aria-checked=false]{color:var(--dsw-alias-label-caption,#8a9099)}
328
+ [data-pages-manager] .pm-empty{display:flex;align-items:center;border-style:dashed;color:var(--dsw-alias-label-caption,#8a9099);font-size:12px;min-height:48px}
329
+ @media(max-width:560px){[data-pages-manager] .pm-identity{flex-basis:100%}[data-pages-manager] .pm-actions{flex:1 1 100%}[data-pages-manager] li{padding:10px}}
330
+ `;
331
+ function PageManager({ entries, railWidth = 64, resolveLabel, actions, t }) {
332
+ const [dragging, setDragging] = import_react.default.useState(null);
333
+ const [dropAt, setDropAt] = import_react.default.useState(null);
334
+ const regionOf = (entry) => REGIONS.includes(entry.region) ? entry.region : "overflow";
335
+ const ordered = [...entries].sort((a, b) => (Number.isFinite(a.order) ? a.order : 0) - (Number.isFinite(b.order) ? b.order : 0));
336
+ const groups = Object.fromEntries(REGIONS.map((region) => [region, ordered.filter((entry) => regionOf(entry) === region)]));
337
+ const endDrag = () => {
338
+ setDragging(null);
339
+ setDropAt(null);
340
+ };
341
+ const normalize = (list) => list.forEach((entry, index) => actions.setPageOrder(entry.id, index));
342
+ const relocate = (id, region, targetId = null, side = "after", promote = false) => {
343
+ const entry = entries.find((item) => item.id === id);
344
+ if (!entry || id === targetId) return;
345
+ const source = regionOf(entry);
346
+ const destination = groups[region].filter((item) => item.id !== id);
347
+ const targetIndex = destination.findIndex((item) => item.id === targetId);
348
+ const index = promote ? 0 : targetIndex < 0 ? destination.length : targetIndex + (side === "after" ? 1 : 0);
349
+ destination.splice(index, 0, entry);
350
+ if (promote) actions.promotePage(id);
351
+ else if (source !== region) actions.setPageRegion(id, region);
352
+ if (source !== region) normalize(groups[source].filter((item) => item.id !== id));
353
+ normalize(destination);
354
+ };
355
+ const move = (entry, region, delta) => {
356
+ const list = [...groups[region]];
357
+ const index = list.findIndex((item) => item.id === entry.id);
358
+ if (index < 0 || index + delta < 0 || index + delta >= list.length) return;
359
+ [list[index], list[index + delta]] = [list[index + delta], list[index]];
360
+ normalize(list);
361
+ };
362
+ const carriedId = (event) => {
363
+ const transferred = event.dataTransfer?.getData(DRAG_TYPE) || event.dataTransfer?.getData("text/plain");
364
+ return entries.some((entry) => entry.id === transferred) ? transferred : dragging;
365
+ };
366
+ const canDrop = () => dragging !== null;
367
+ const regionTitle = (region) => t(region === "top" ? "managerTop" : region === "bottom" ? "managerBottom" : "managerHidden");
368
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-pages-manager": "", children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { "data-pages-manager-style": "", children: styles }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [
371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("managerTitle") }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("managerHint") })
373
+ ] }),
374
+ actions.setRailWidth && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { "aria-label": t("navigationWidth"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("navigationWidth") }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
377
+ "input",
378
+ {
379
+ "aria-label": t("navigationWidth"),
380
+ type: "range",
381
+ min: "40",
382
+ max: "96",
383
+ step: "1",
384
+ value: railWidth,
385
+ onChange: (event) => actions.setRailWidth(Number(event.target.value))
386
+ }
387
+ ),
388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("output", { children: [
389
+ railWidth,
390
+ " px"
391
+ ] }),
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => actions.setRailWidth(64), children: t("resetWidth") })
393
+ ] }) }),
394
+ REGIONS.map((region) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-label": regionTitle(region), children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
396
+ regionTitle(region),
397
+ " ",
398
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: groups[region].length })
399
+ ] }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
401
+ "ul",
402
+ {
403
+ "aria-label": regionTitle(region),
404
+ "data-group": region,
405
+ "data-drop": dropAt?.region === region && dropAt.id === null,
406
+ onDragOver: (event) => {
407
+ if (!canDrop(region) || event.target !== event.currentTarget && groups[region].length) return;
408
+ event.preventDefault();
409
+ event.dataTransfer.dropEffect = "move";
410
+ setDropAt({ region, id: null, side: "after" });
411
+ },
412
+ onDragLeave: (event) => {
413
+ if (!event.currentTarget.contains(event.relatedTarget)) setDropAt(null);
414
+ },
415
+ onDrop: (event) => {
416
+ event.preventDefault();
417
+ const id = carriedId(event);
418
+ if (id !== null) relocate(id, region);
419
+ endDrag();
420
+ },
421
+ children: [
422
+ groups[region].length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "pm-empty", children: t("managerEmpty") }),
423
+ groups[region].map((entry, index) => {
424
+ const label = resolveLabel(entry);
425
+ const visible = region !== "overflow";
426
+ const visibilityKey = visible ? "managerHide" : "managerShow";
427
+ const Icon = entry.icon;
428
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
429
+ "li",
430
+ {
431
+ "aria-label": label,
432
+ draggable: true,
433
+ "data-dragging": dragging === entry.id,
434
+ "data-insert": dropAt?.region === region && dropAt.id === entry.id ? dropAt.side : void 0,
435
+ onDragStart: (event) => {
436
+ if (event.target.closest?.("button,select")) {
437
+ event.preventDefault();
438
+ return;
439
+ }
440
+ event.dataTransfer.setData(DRAG_TYPE, entry.id);
441
+ event.dataTransfer.setData("text/plain", entry.id);
442
+ event.dataTransfer.effectAllowed = "move";
443
+ setDragging(entry.id);
444
+ },
445
+ onDragEnd: endDrag,
446
+ onDragOver: (event) => {
447
+ event.stopPropagation();
448
+ if (!canDrop(region) || dragging === entry.id) return;
449
+ event.preventDefault();
450
+ event.dataTransfer.dropEffect = "move";
451
+ const bounds = event.currentTarget.getBoundingClientRect();
452
+ setDropAt({ region, id: entry.id, side: event.clientY < bounds.top + bounds.height / 2 ? "before" : "after" });
453
+ },
454
+ onDrop: (event) => {
455
+ event.preventDefault();
456
+ event.stopPropagation();
457
+ const bounds = event.currentTarget.getBoundingClientRect();
458
+ relocate(carriedId(event), region, entry.id, event.clientY < bounds.top + bounds.height / 2 ? "before" : "after");
459
+ endDrag();
460
+ },
461
+ children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pm-identity", children: [
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pm-grip", "aria-hidden": "true", children: "\u283F" }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pm-icon", "aria-hidden": "true", children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {}) : entry.glyph }),
465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pm-name", children: label }),
467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pm-id", children: entry.id })
468
+ ] })
469
+ ] }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pm-actions", children: [
471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
472
+ "select",
473
+ {
474
+ "aria-label": `${t("managerGroup")} ${label}`,
475
+ value: region,
476
+ onChange: (event) => relocate(entry.id, event.target.value),
477
+ children: REGIONS.map((value) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value, children: regionTitle(value) }, value))
478
+ }
479
+ ),
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
481
+ "button",
482
+ {
483
+ type: "button",
484
+ role: "switch",
485
+ "aria-checked": visible,
486
+ "aria-label": `${t(visibilityKey)} ${label}`,
487
+ onClick: () => relocate(entry.id, visible ? "overflow" : "top"),
488
+ children: t(visibilityKey)
489
+ }
490
+ ),
491
+ region === "overflow" && actions.setPagePinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
492
+ "button",
493
+ {
494
+ type: "button",
495
+ "aria-pressed": Boolean(entry.pinned),
496
+ "aria-label": `${t(entry.pinned ? "unpin" : "pin")} ${label}`,
497
+ onClick: () => actions.setPagePinned(entry.id, !entry.pinned),
498
+ children: t(entry.pinned ? "unpin" : "pin")
499
+ }
500
+ ),
501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `${t("managerUp")} ${label}`, disabled: index === 0, onClick: () => move(entry, region, -1), children: "\u2191" }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `${t("managerDown")} ${label}`, disabled: index === groups[region].length - 1, onClick: () => move(entry, region, 1), children: "\u2193" }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ "aria-label": `${t("managerPin")} ${label}`,
508
+ disabled: region === "top" && index === 0,
509
+ onClick: () => relocate(entry.id, "top", null, "before", true),
510
+ children: "\u21C8"
511
+ }
512
+ ),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `${t("managerOpen")} ${label}`, onClick: () => actions.selectPage(entry.id), children: t("managerOpen") })
514
+ ] })
515
+ ]
516
+ },
517
+ entry.id
518
+ );
519
+ })
520
+ ]
521
+ }
522
+ )
523
+ ] }, region))
524
+ ] });
525
+ }
526
+
527
+ // src/page-shell.jsx
528
+ var import_jsx_runtime2 = require("react/jsx-runtime");
529
+ var namespace = "dsh-pages";
530
+ var translations = {
531
+ en: {
532
+ navigation: "Pages",
533
+ back: "Back to workbench",
534
+ more: "More pages",
535
+ pin: "Pin to top group",
536
+ unpin: "Unpin",
537
+ managerTitle: "Page manager",
538
+ managerHint: "Arrange the top, recycle and bottom groups. Recycled pages remain accessible in \u2026; pin them to append shortcuts after \u2026 in the top group.",
539
+ managerTop: "Top group",
540
+ managerBottom: "Bottom group",
541
+ managerHidden: "Recycle group",
542
+ managerEmpty: "No pages here.",
543
+ managerUp: "Move up",
544
+ managerDown: "Move down",
545
+ managerShow: "Show",
546
+ managerHide: "Hide",
547
+ navigationWidth: "Navigation width",
548
+ resetWidth: "Reset width",
549
+ managerPin: "Move to top",
550
+ managerOpen: "Open",
551
+ managerGroup: "Group"
552
+ },
553
+ zh: {
554
+ navigation: "\u9875\u9762",
555
+ back: "\u8FD4\u56DE\u5DE5\u4F5C\u53F0",
556
+ more: "\u66F4\u591A\u9875\u9762",
557
+ pin: "\u56FA\u5B9A\u5230\u9876\u90E8\u7EC4\u672B\u5C3E",
558
+ unpin: "\u53D6\u6D88\u56FA\u5B9A",
559
+ managerTitle: "\u9875\u9762\u7BA1\u7406",
560
+ managerHint: "\u7BA1\u7406\u9876\u90E8\u9ED8\u8BA4\u7EC4\u3001\u56DE\u6536\u7EC4\u548C\u5E95\u90E8\u7EC4\u3002\u6536\u8D77\u7684\u9875\u9762\u4ECD\u53EF\u4ECE \u2026 \u6253\u5F00\uFF1B\u56FA\u5B9A\u540E\u5728\u9876\u90E8\u7EC4\u7684 \u2026 \u4E0B\u65B9\u8FFD\u52A0\u5165\u53E3\u3002",
561
+ managerTop: "\u9876\u90E8\u7EC4",
562
+ managerBottom: "\u5E95\u90E8\u7EC4",
563
+ managerHidden: "\u56DE\u6536\u7EC4",
564
+ managerEmpty: "\u8FD9\u91CC\u6CA1\u6709\u9875\u9762\u3002",
565
+ managerUp: "\u4E0A\u79FB",
566
+ managerDown: "\u4E0B\u79FB",
567
+ managerShow: "\u663E\u793A",
568
+ managerHide: "\u9690\u85CF",
569
+ navigationWidth: "\u5BFC\u822A\u5BBD\u5EA6",
570
+ resetWidth: "\u6062\u590D\u9ED8\u8BA4\u5BBD\u5EA6",
571
+ managerPin: "\u79FB\u5230\u6700\u524D",
572
+ managerOpen: "\u6253\u5F00",
573
+ managerGroup: "\u5206\u7EC4"
574
+ }
575
+ };
576
+ var secondary = "var(--dsw-alias-label-secondary, #aaa)";
577
+ var border = "1px solid var(--dsw-alias-border-l2, #555)";
578
+ var surface = "var(--dsw-alias-bg-layer-2, #181818)";
579
+ var foreground = "var(--dsw-alias-label-primary, #ddd)";
580
+ var button = {
581
+ font: "inherit",
582
+ fontSize: 13,
583
+ color: foreground,
584
+ background: surface,
585
+ border,
586
+ borderRadius: 6,
587
+ padding: "9px 14px",
588
+ cursor: "pointer"
589
+ };
590
+ var layoutCss = (width) => `
591
+ #root:has([data-dsh-pages-shell]) { box-sizing: border-box; padding-left: ${width}px; }
592
+ [data-dsh-pages-rail] button:hover { background: var(--dsw-alias-interactive-bg-hover, #333); }
593
+ [data-dsh-pages-shell] button:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
594
+ `;
595
+ function RailIcon({ width, children }) {
596
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-dsh-pages-rail-icon": "", "aria-hidden": "true", style: {
597
+ display: "grid",
598
+ gridTemplateColumns: "23px",
599
+ gridTemplateRows: "23px",
600
+ placeItems: "center",
601
+ width: 23,
602
+ height: 23,
603
+ lineHeight: 1,
604
+ transform: `scale(${width / 64})`,
605
+ transformOrigin: "center"
606
+ }, children });
607
+ }
608
+ function PageIcon({ entry, home }) {
609
+ if (entry.icon) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(entry.icon, {});
610
+ if (entry.id === home) {
611
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "23", height: "23", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.65", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m3 10 9-7 9 7M6 9v12h12V9M10 21v-7h4v7" }) });
612
+ }
613
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "23", height: "23", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.65", "aria-hidden": "true", children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "7", height: "7", rx: "1.6" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "14", y: "4", width: "7", height: "7", rx: "1.6" }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "13", width: "7", height: "7", rx: "1.6" }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "14", y: "13", width: "7", height: "7", rx: "1.6" })
618
+ ] });
619
+ }
620
+ function PageShell(props) {
621
+ const snapshot = props.usePages((value) => value);
622
+ const railWidth = snapshot.railWidth ?? 64;
623
+ const scale = railWidth / 64;
624
+ const railButton = {
625
+ width: 46 * scale,
626
+ height: 46 * scale,
627
+ minHeight: 46 * scale,
628
+ padding: 0,
629
+ boxSizing: "border-box",
630
+ display: "grid",
631
+ placeItems: "center",
632
+ flexShrink: 0,
633
+ border: "none",
634
+ borderRadius: 9 * scale,
635
+ cursor: "pointer",
636
+ font: "inherit",
637
+ lineHeight: 1
638
+ };
639
+ const home = props.homePage;
640
+ const open = snapshot.activePage !== home;
641
+ const manager = snapshot.activePage === props.managerPage;
642
+ const close = (0, import_react2.useRef)(null);
643
+ const shell = (0, import_react2.useRef)(null);
644
+ const moreButton = (0, import_react2.useRef)(null);
645
+ const contentId = (0, import_react2.useId)();
646
+ const [menu, setMenu] = (0, import_react2.useState)(null);
647
+ const active = snapshot.pages.find((entry) => entry.id === snapshot.activePage);
648
+ const top = snapshot.pages.filter((entry) => entry.region === "top");
649
+ const parked = snapshot.pages.filter((entry) => entry.region === "overflow");
650
+ const menuTop = menu === "more" && moreButton.current !== null ? Math.max(12, Math.min(moreButton.current.getBoundingClientRect().top, globalThis.innerHeight - 260)) : 12;
651
+ const dismiss = () => props.selectPage(home);
652
+ (0, import_react2.useEffect)(() => {
653
+ if (!open) return;
654
+ close.current?.focus();
655
+ const containFocus = (event) => {
656
+ if (!shell.current?.contains(event.target)) close.current?.focus();
657
+ };
658
+ document.addEventListener("focusin", containFocus);
659
+ return () => document.removeEventListener("focusin", containFocus);
660
+ }, [open, snapshot.activePage]);
661
+ (0, import_react2.useEffect)(() => {
662
+ if (menu === null) return void 0;
663
+ const onPointer = (event) => {
664
+ if (event.target.closest?.("[data-dsh-pages-more], [data-dsh-pages-more-panel]")) return;
665
+ setMenu(null);
666
+ };
667
+ const onKey = (event) => {
668
+ if (event.key === "Escape") setMenu(null);
669
+ };
670
+ document.addEventListener("pointerdown", onPointer, true);
671
+ document.addEventListener("keydown", onKey, true);
672
+ return () => {
673
+ document.removeEventListener("pointerdown", onPointer, true);
674
+ document.removeEventListener("keydown", onKey, true);
675
+ };
676
+ }, [menu]);
677
+ const keyDown = (event) => {
678
+ if (!open) return;
679
+ event.stopPropagation();
680
+ if (event.key === "Escape") {
681
+ event.preventDefault();
682
+ dismiss();
683
+ } else if (event.key === "Tab") {
684
+ const controls = Array.from(shell.current.querySelectorAll('button:not(:disabled), input:not(:disabled), select:not(:disabled), a[href], [tabindex="0"]')).filter((node) => node.getClientRects().length > 0);
685
+ const index = controls.indexOf(document.activeElement);
686
+ const next = event.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
687
+ event.preventDefault();
688
+ (controls[next] || shell.current).focus();
689
+ }
690
+ };
691
+ const navItem = (entry) => {
692
+ const selected = entry.id === snapshot.activePage;
693
+ const label = props.resolveLabel(entry.label);
694
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
695
+ "button",
696
+ {
697
+ type: "button",
698
+ title: label,
699
+ "aria-label": label,
700
+ "aria-current": selected ? "page" : void 0,
701
+ "aria-controls": entry.id === home ? void 0 : contentId,
702
+ onClick: () => props.selectPage(entry.id),
703
+ style: {
704
+ ...railButton,
705
+ color: selected ? foreground : secondary,
706
+ background: selected ? "var(--dsw-alias-interactive-bg-hover, #333)" : "transparent",
707
+ boxShadow: selected ? "inset 2px 0 0 currentColor" : "none"
708
+ },
709
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RailIcon, { width: railWidth, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PageIcon, { entry, home }) })
710
+ },
711
+ entry.id
712
+ );
713
+ };
714
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
715
+ "div",
716
+ {
717
+ ref: shell,
718
+ "data-dsh-pages-shell": "",
719
+ "data-active-page": snapshot.activePage,
720
+ role: open ? "dialog" : void 0,
721
+ "aria-modal": open ? true : void 0,
722
+ "aria-label": open ? props.resolveLabel(active?.label ?? "") : void 0,
723
+ tabIndex: open ? -1 : void 0,
724
+ style: { position: "fixed", inset: 0, zIndex: 1e3, pointerEvents: "none", color: foreground, fontFamily: "inherit" },
725
+ onKeyDown: keyDown,
726
+ onKeyUp: (event) => {
727
+ if (open) event.stopPropagation();
728
+ },
729
+ children: [
730
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { "data-dsh-pages-layout": "", children: layoutCss(railWidth) }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("nav", { "data-dsh-pages-rail": "", "aria-label": props.t("navigation"), style: {
732
+ position: "absolute",
733
+ inset: "0 auto 0 0",
734
+ width: railWidth,
735
+ boxSizing: "border-box",
736
+ background: surface,
737
+ borderRight: border,
738
+ display: "flex",
739
+ flexDirection: "column",
740
+ alignItems: "center",
741
+ gap: 8 * scale,
742
+ padding: `${16 * scale}px ${8 * scale}px`,
743
+ pointerEvents: "auto",
744
+ overflowY: "auto",
745
+ overflowX: "hidden"
746
+ }, children: [
747
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", style: { display: "grid", placeItems: "center", flexShrink: 0, width: 46 * scale, fontSize: 11 * scale, lineHeight: 1, letterSpacing: "0.08em", fontWeight: 700, padding: `${4 * scale}px 0 ${20 * scale}px`, color: secondary }, children: "P /" }),
748
+ top.map(navItem),
749
+ parked.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
750
+ "button",
751
+ {
752
+ ref: moreButton,
753
+ type: "button",
754
+ "data-dsh-pages-more": "",
755
+ title: props.t("more"),
756
+ "aria-label": props.t("more"),
757
+ "aria-expanded": menu === "more",
758
+ "aria-haspopup": "menu",
759
+ onClick: () => setMenu(menu === "more" ? null : "more"),
760
+ style: {
761
+ ...railButton,
762
+ color: parked.some((entry) => entry.id === snapshot.activePage) ? foreground : secondary,
763
+ background: menu === "more" ? "var(--dsw-alias-interactive-bg-hover, #333)" : "transparent"
764
+ },
765
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RailIcon, { width: railWidth, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "23", height: "23", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1.9" }),
767
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1.9" }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1.9" })
769
+ ] }) })
770
+ }
771
+ ),
772
+ parked.some((entry) => entry.pinned) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-dsh-pages-pin-divider": "", style: { display: "block", flexShrink: 0, width: 28 * scale, borderTop: border } }),
773
+ parked.filter((entry) => entry.pinned).sort((a, b) => a.pinOrder - b.pinOrder).map(navItem),
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1 } }),
775
+ snapshot.pages.filter((entry) => entry.region === "bottom").map(navItem)
776
+ ] }),
777
+ menu === "more" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-dsh-pages-more-panel": "", role: "menu", "aria-label": props.t("more"), style: {
778
+ position: "fixed",
779
+ top: menuTop,
780
+ left: railWidth + 8,
781
+ zIndex: 1001,
782
+ minWidth: 200,
783
+ maxHeight: "60vh",
784
+ overflowY: "auto",
785
+ padding: 6,
786
+ boxSizing: "border-box",
787
+ pointerEvents: "auto",
788
+ background: surface,
789
+ border,
790
+ borderRadius: 10,
791
+ boxShadow: "0 10px 32px #0007"
792
+ }, children: parked.map((entry) => {
793
+ const label = props.resolveLabel(entry.label);
794
+ const selected = entry.id === snapshot.activePage;
795
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-dsh-pages-more-row": "", style: { display: "flex", alignItems: "center", gap: 4 }, children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
797
+ "button",
798
+ {
799
+ type: "button",
800
+ role: "menuitem",
801
+ "aria-current": selected ? "page" : void 0,
802
+ onClick: () => {
803
+ setMenu(null);
804
+ props.selectPage(entry.id);
805
+ },
806
+ style: {
807
+ display: "flex",
808
+ alignItems: "center",
809
+ gap: 10,
810
+ flex: 1,
811
+ minWidth: 0,
812
+ minHeight: 36,
813
+ padding: "0 10px",
814
+ border: "none",
815
+ borderRadius: 7,
816
+ cursor: "pointer",
817
+ font: "inherit",
818
+ textAlign: "left",
819
+ color: selected ? foreground : secondary,
820
+ background: selected ? "var(--dsw-alias-interactive-bg-hover, #333)" : "transparent"
821
+ },
822
+ children: [
823
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { display: "grid", placeItems: "center", width: 20, height: 20 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PageIcon, { entry, home }) }),
824
+ label
825
+ ]
826
+ }
827
+ ),
828
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
829
+ "button",
830
+ {
831
+ type: "button",
832
+ title: props.t(entry.pinned ? "unpin" : "pin"),
833
+ "aria-label": `${props.t(entry.pinned ? "unpin" : "pin")} ${label}`,
834
+ "data-dsh-pages-pin": "",
835
+ "aria-pressed": Boolean(entry.pinned),
836
+ onClick: () => props.setPagePinned(entry.id, !entry.pinned),
837
+ style: {
838
+ width: 32,
839
+ minHeight: 32,
840
+ display: "grid",
841
+ placeItems: "center",
842
+ flexShrink: 0,
843
+ border: "none",
844
+ borderRadius: 6,
845
+ cursor: "pointer",
846
+ font: "inherit",
847
+ color: secondary,
848
+ background: "transparent"
849
+ },
850
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 2 22 10l-2.6 2.6-1.4-1.4-3.5 3.5.7 4.3-2.1 2.1-3.5-3.5L4 23l-1-1 4.5-5.5L4 13l2.1-2.1 4.3.7 3.5-3.5-1.4-1.4z" }) })
851
+ }
852
+ )
853
+ ] }, entry.id);
854
+ }) }),
855
+ open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { id: contentId, "aria-labelledby": contentId, style: {
856
+ position: "absolute",
857
+ inset: `0 0 0 ${railWidth}px`,
858
+ overflow: "auto",
859
+ boxSizing: "border-box",
860
+ pointerEvents: "auto",
861
+ background: surface,
862
+ color: foreground,
863
+ fontSize: 14,
864
+ lineHeight: 1.6,
865
+ padding: "24px clamp(16px, 5vw, 80px)"
866
+ }, children: [
867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { style: { borderBottom: border, paddingBottom: 24, display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", ref: close, style: button, onClick: dismiss, children: [
869
+ "\u2190 ",
870
+ props.t("back")
871
+ ] }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: secondary, fontSize: 11, letterSpacing: "0.12em" }, children: props.resolveLabel(active?.label ?? "") })
873
+ ] }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { maxWidth: 760, margin: "clamp(32px, 8vh, 100px) auto" }, children: manager ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
875
+ PageManager,
876
+ {
877
+ entries: snapshot.pages,
878
+ railWidth,
879
+ resolveLabel: (entry) => props.resolveLabel(entry.label),
880
+ t: props.t,
881
+ actions: {
882
+ selectPage: props.selectPage,
883
+ setPageRegion: props.setPageRegion,
884
+ setPagePinned: props.setPagePinned,
885
+ setRailWidth: props.setRailWidth,
886
+ setPageOrder: props.setPageOrder,
887
+ promotePage: props.promotePage
888
+ }
889
+ }
890
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.default.Fragment, { children: props.renderSlot(props.pageSlot, {}, { entryKey: snapshot.activePage }) }, snapshot.activePage) })
891
+ ] })
892
+ ]
893
+ }
894
+ );
895
+ }
896
+ function installPageShell(ctx, config = {}) {
897
+ const { slots } = ctx;
898
+ const cleanups = [];
899
+ let disposed = false;
900
+ const dispose = () => {
901
+ if (disposed) return;
902
+ disposed = true;
903
+ let failure;
904
+ for (const cleanup of cleanups.splice(0).reverse()) {
905
+ try {
906
+ cleanup();
907
+ } catch (error) {
908
+ failure ??= error;
909
+ }
910
+ }
911
+ if (failure) throw failure;
912
+ };
913
+ return ctx.effect(() => {
914
+ try {
915
+ const pages = createPages(config);
916
+ cleanups.push(() => pages.dispose());
917
+ cleanups.push(ctx.reflect.provide("pages", pages.service));
918
+ cleanups.push(ctx.locale.register(namespace, translations));
919
+ cleanups.push(slots.inject("shell.overlay", () => slots.register({
920
+ name: "shell.overlay",
921
+ id: "dsh-pages.rail",
922
+ children: { [pages.service.config.pageSlot]: { kind: "keyed", scope: "root" } },
923
+ inject: () => ({
924
+ hooks: { pages: { getSnapshot: pages.state.getSnapshot, subscribe: pages.state.subscribe } },
925
+ homePage: pages.service.config.homePage,
926
+ managerPage: pages.service.config.managerPage,
927
+ pageSlot: pages.service.config.pageSlot,
928
+ selectPage: pages.service.selectPage,
929
+ setPageRegion: pages.service.setPageRegion,
930
+ setPagePinned: pages.service.setPagePinned,
931
+ setRailWidth: pages.service.setRailWidth,
932
+ setPageOrder: pages.service.setPageOrder,
933
+ promotePage: pages.service.promotePage,
934
+ resolveLabel: (value) => typeof value === "string" ? value : ctx.locale.resolveText(value)
935
+ }),
936
+ locale: namespace
937
+ }, PageShell)));
938
+ return dispose;
939
+ } catch (error) {
940
+ try {
941
+ dispose();
942
+ } catch {
943
+ }
944
+ throw error;
945
+ }
946
+ }, "dsh-pages: page shell");
947
+ }
948
+
949
+ // src/client.jsx
950
+ var name = "dsh-pages-client";
951
+ var inject = { slots: { required: true }, locale: { required: true } };
952
+ function apply(ctx, config = {}) {
953
+ return applyWithConfig(ctx, config);
954
+ }
955
+ function applyWithConfig(ctx, config = {}) {
956
+ return installPageShell(ctx, normalizeConfig(config));
957
+ }
958
+ return module.exports;}});