@dsh-plugin/dsh-ui-experience 0.0.1 → 1.0.0-dev.43486821486

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 CHANGED
@@ -1,467 +1,1243 @@
1
- "use strict";
2
- (() => {
3
- // src/shared.ts
4
- var REMOTE_SERVICE = "uiExperience";
5
- var BOOT_ATTRIBUTE = "data-dsh-uix-matugen";
6
- var DEFAULT_COLORS_PATH = "~/.local/state/quickshell/user/generated/colors.json";
7
- var DEFAULT_TOKENS_PATH = "~/.local/state/quickshell/user/generated/dsh-tokens.json";
8
-
9
- // src/client.ts
10
- var SOURCE = "@dsh-plugin/dsh-ui-experience";
11
- var POLL_MS = 2e3;
12
- var IDLE_TICKS = 15;
13
- var BORDER_WIDTH = /^(?:0|[0-9]+(?:\.[0-9]+)?(?:px|rem|em))$/;
14
- function uiExperienceRemoteContribution(zod) {
15
- const codec = (name, schema) => ({
16
- mode: "strict",
17
- typeSymbol: `${SOURCE}/shared#${name}`,
18
- create: schema
19
- });
20
- const descriptor = (method, shape, typeName) => ({
21
- id: `${SOURCE}#${REMOTE_SERVICE}/${method}`,
22
- service: REMOTE_SERVICE,
23
- namespace: REMOTE_SERVICE,
24
- method,
25
- invocation: { kind: "direct" },
26
- parameters: [{
27
- name: "args",
28
- wire: "args",
29
- source: "json",
30
- codec: codec(typeName, () => zod.object(shape())),
31
- strictCodec: codec(typeName, () => zod.strictObject(shape()))
32
- }],
33
- result: codec(`${method}Result`, () => zod.object({}))
34
- });
35
- return {
36
- package: SOURCE,
37
- descriptors: [
38
- descriptor("poll", () => ({ known: zod.string().optional() }), "PollArgs"),
39
- descriptor("setActive", () => ({ active: zod.boolean().optional() }), "SetActiveArgs"),
40
- descriptor("setPaths", () => ({ colorsPath: zod.string().optional(), tokensPath: zod.string().optional() }), "SetPathsArgs")
41
- ]
42
- };
43
- }
44
- var DICTS = {
45
- zh: {
46
- "palette.title": "Matugen \u914D\u8272\u8DDF\u968F",
47
- "palette.description": "\u5C06\u7CFB\u7EDF matugen \u751F\u6210\u7684 Material You \u914D\u8272\u53E0\u52A0\u5728\u5F53\u524D\u4E3B\u9898\u4E0A\uFF0C\u914D\u8272\u6587\u4EF6\u91CD\u65B0\u751F\u6210\u540E\u81EA\u52A8\u8DDF\u968F",
48
- "palette.persist-failed": "\u5F00\u5173\u72B6\u6001\u4FDD\u5B58\u5931\u8D25",
49
- "palette.save-failed": "\u8DEF\u5F84\u4FDD\u5B58\u5931\u8D25",
50
- "palette.saved": "\u5DF2\u4FDD\u5B58",
51
- "palette.paths": "\u914D\u8272\u6587\u4EF6\u8DEF\u5F84",
52
- "palette.colors-path": "matugen \u989C\u8272\u6587\u4EF6",
53
- "palette.tokens-path": "DSH \u4EE4\u724C\u6587\u4EF6\uFF08\u5B58\u5728\u65F6\u4F18\u5148\uFF09",
54
- "palette.save": "\u4FDD\u5B58",
55
- "palette.reset": "\u6062\u590D\u9ED8\u8BA4",
56
- "palette.paths-hint": "\u652F\u6301\u4EE5 ~ \u5F00\u5934\u7684\u8DEF\u5F84\uFF0C\u6216\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4EE4\u724C\u6587\u4EF6\u5B58\u5728\u65F6\u4F18\u5148\u4F7F\u7528\uFF0C\u5426\u5219\u8BFB\u53D6\u989C\u8272\u6587\u4EF6\u5E76\u6620\u5C04\u3002"
57
- },
58
- en: {
59
- "palette.title": "Matugen palette sync",
60
- "palette.description": "Overlay the system matugen Material You palette on the current theme; follows the file when matugen regenerates it",
61
- "palette.persist-failed": "Failed to save the switch state",
62
- "palette.save-failed": "Failed to save the paths",
63
- "palette.saved": "Saved",
64
- "palette.paths": "Palette file paths",
65
- "palette.colors-path": "matugen colors file",
66
- "palette.tokens-path": "DSH token file (preferred when present)",
67
- "palette.save": "Save",
68
- "palette.reset": "Restore defaults",
69
- "palette.paths-hint": "Paths may start with ~, or be absolute. The token file wins when it exists; otherwise the colors file is read and mapped."
70
- }
71
- };
72
- var CSS = `
73
- .dsh-uix-row{display:block;padding:16px 0;border-bottom:0.5px solid var(--dsw-alias-border-l2)}
74
- .dsh-uix-head{display:flex;align-items:center;justify-content:space-between;gap:24px}
75
- .dsh-uix-text{min-width:0}
76
- .dsh-uix-title{font-size:14px;line-height:22px}
77
- .dsh-uix-desc{margin-top:4px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
78
- .dsh-uix-error{margin-top:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
79
- .dsh-uix-notice{margin-top:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
80
- .dsh-uix-disclosure{margin-top:8px;padding:0;border:0;background:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;cursor:pointer}
81
- .dsh-uix-disclosure:hover{color:var(--dsw-alias-label-primary)}
82
- .dsh-uix-disclosure:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:4px}
83
- .dsh-uix-paths{margin-top:10px;display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
84
- .dsh-uix-field{display:flex;flex-direction:column;gap:4px}
85
- .dsh-uix-label{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
86
- .dsh-uix-input{box-sizing:border-box;width:100%;height:32px;padding:0 10px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,monospace);font-size:12px;outline:none}
87
- .dsh-uix-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:1px}
88
- .dsh-uix-actions{display:flex;gap:8px}
89
- .dsh-uix-button{height:28px;padding:0 12px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:12px;cursor:pointer}
90
- .dsh-uix-button:hover:enabled{background:var(--dsw-alias-interactive-bg-hover)}
91
- .dsh-uix-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:2px}
92
- .dsh-uix-button-primary{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-on-color)}
93
- .dsh-uix-button:disabled{opacity:.5;cursor:default}
94
- .dsh-uix-hint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
95
- .dsh-uix-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
96
- .dsh-uix-switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}
97
- .dsh-uix-switch:disabled{cursor:default;opacity:.5}
98
- .dsh-uix-switch:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:2px}
99
- .dsh-uix-switch-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
100
- .dsh-uix-switch[aria-checked=false] .dsh-uix-switch-thumb{background:var(--dsw-alias-switch-thumb)}
101
- .dsh-uix-switch[aria-checked=true] .dsh-uix-switch-thumb{transform:translate(16px)}
102
- `;
103
- function registerClient() {
104
- window.__ModuleLoader__.load({
105
- id: SOURCE,
106
- factory(require2) {
107
- const React = require2("react");
108
- const zod = require2("zod");
109
- const h = React.createElement;
110
- function run(registry, ctx, gateway) {
111
- const t = ctx.locale.bind("ui-experience");
112
- ctx.effect(() => ctx.locale.register("ui-experience", DICTS), "ui-experience: dictionaries");
113
- ctx.effect(() => {
114
- const sheet = document.createElement("style");
115
- sheet.textContent = CSS;
116
- document.head.appendChild(sheet);
117
- return () => {
118
- sheet.remove();
119
- };
120
- }, "ui-experience: row styles");
121
- let disposed = false;
122
- let known;
123
- let bootstrapped = false;
124
- let writing = false;
125
- let inflight = false;
126
- let idleTick = 0;
127
- let lastRpcProblem = "";
128
- let lastPaletteProblem = "";
129
- let paintedWidth = "";
130
- let disposeOverride;
131
- let borderSheet;
132
- let view = {
133
- active: false,
134
- borderWidth: "0.5px",
135
- paths: { colors: DEFAULT_COLORS_PATH, tokens: DEFAULT_TOKENS_PATH },
136
- problem: "",
137
- notice: "",
138
- saving: false
139
- };
140
- const listeners = /* @__PURE__ */ new Set();
141
- const publish = (patch) => {
142
- view = { ...view, ...patch };
143
- for (const listener of listeners) listener();
144
- };
145
- const toOverrides = (modes) => {
146
- const out = {};
147
- for (const [token, dark] of Object.entries(modes.dark)) {
148
- const light = modes.light[token] ?? dark;
149
- if (typeof light === "string" && light !== "") out[token] = { light, dark };
150
- }
151
- return out;
152
- };
153
- const clearOverride = () => {
154
- disposeOverride?.();
155
- disposeOverride = void 0;
156
- };
157
- const applyPalette = (modes) => {
158
- if (disposed) return;
159
- clearOverride();
160
- try {
161
- disposeOverride = registry.overrideTokens(SOURCE, toOverrides(modes));
162
- } catch (error) {
163
- const message = `theme override rejected: ${error.message}`;
164
- console.error(`ui-experience: ${message}`);
165
- publish({ problem: message });
166
- }
167
- };
168
- const applyBorder = (width) => {
169
- if (disposed) return;
170
- if (!BORDER_WIDTH.test(width)) {
171
- const message = `borderWidth ${JSON.stringify(width)} is not a CSS length; borders left untouched`;
172
- console.error(`ui-experience: ${message}`);
173
- publish({ problem: message });
174
- return;
175
- }
176
- if (width === paintedWidth) return;
177
- paintedWidth = width;
178
- borderSheet?.remove();
179
- borderSheet = document.createElement("style");
180
- borderSheet.textContent = `body :where(*:not([data-dsh-uix-keep-border])){border-width:${width} !important}`;
181
- document.head.appendChild(borderSheet);
182
- };
183
- const clearBorder = () => {
184
- borderSheet?.remove();
185
- borderSheet = void 0;
186
- paintedWidth = "";
187
- };
188
- const setBootAttribute = (on) => {
189
- if (on) document.documentElement.setAttribute(BOOT_ATTRIBUTE, "");
190
- else document.documentElement.removeAttribute(BOOT_ATTRIBUTE);
191
- };
192
- const teardown = () => {
193
- clearOverride();
194
- clearBorder();
195
- setBootAttribute(false);
196
- };
197
- const unwrap = (raw) => {
198
- const envelope = raw;
199
- if (envelope.ok === false) throw new Error(envelope.error?.message ?? "remote call failed");
200
- return envelope.value;
201
- };
202
- const adopt = (answer) => {
203
- if (answer.paths.colors !== view.paths.colors || answer.paths.tokens !== view.paths.tokens || answer.borderWidth !== view.borderWidth) {
204
- publish({ paths: answer.paths, borderWidth: answer.borderWidth });
205
- }
206
- if (!bootstrapped) {
207
- bootstrapped = true;
208
- setBootAttribute(answer.active);
209
- publish({ active: answer.active });
210
- } else if (!writing && view.active !== answer.active) {
211
- publish({ active: answer.active });
212
- if (answer.active) {
213
- setBootAttribute(true);
214
- if (answer.status === "ok" && answer.tokens !== void 0) applyPalette(answer.tokens);
215
- applyBorder(answer.borderWidth);
216
- } else {
217
- teardown();
218
- }
219
- }
220
- if (answer.status !== "ok") {
221
- if (answer.message !== lastPaletteProblem) {
222
- lastPaletteProblem = answer.message;
223
- console.error(`ui-experience: palette unavailable: ${answer.message}`);
224
- }
225
- publish({ problem: answer.message });
226
- return;
227
- }
228
- if (lastPaletteProblem !== "") {
229
- lastPaletteProblem = "";
230
- publish({ problem: "" });
231
- }
232
- if (answer.changed) {
233
- known = answer.version;
234
- if (view.active && answer.tokens !== void 0) applyPalette(answer.tokens);
235
- }
236
- if (view.active) applyBorder(answer.borderWidth);
237
- };
238
- const poll = async () => {
239
- if (disposed || inflight) return;
240
- if (bootstrapped && !view.active) {
241
- idleTick = (idleTick + 1) % IDLE_TICKS;
242
- if (idleTick !== 0) return;
243
- } else {
244
- idleTick = 0;
245
- }
246
- inflight = true;
247
- let answer;
248
- try {
249
- answer = unwrap(await gateway.poll({ known }));
250
- } catch (error) {
251
- const message = error.message;
252
- if (message !== lastRpcProblem) {
253
- lastRpcProblem = message;
254
- console.error(`ui-experience: poll RPC failed: ${message}`);
255
- }
256
- return;
257
- } finally {
258
- inflight = false;
259
- }
260
- if (disposed) return;
261
- lastRpcProblem = "";
262
- adopt(answer);
263
- };
264
- const setActive = async (next) => {
265
- if (disposed || writing) return;
266
- writing = true;
267
- publish({ saving: true, problem: "", notice: "" });
268
- try {
269
- if (!next) {
270
- await gateway.setActive({ active: false });
271
- if (disposed) return;
272
- publish({ active: false });
273
- teardown();
274
- return;
275
- }
276
- const answer = unwrap(await gateway.poll({}));
277
- if (disposed) return;
278
- if (answer.status !== "ok") {
279
- publish({ problem: answer.message });
280
- return;
281
- }
282
- const tokens = answer.tokens;
283
- if (tokens === void 0) throw new Error("poll returned no tokens for an activation request");
284
- await gateway.setActive({ active: true });
285
- if (disposed) return;
286
- known = answer.version;
287
- bootstrapped = true;
288
- setBootAttribute(true);
289
- publish({ active: true, paths: answer.paths, notice: t("palette.saved") });
290
- applyPalette(tokens);
291
- applyBorder(answer.borderWidth);
292
- } catch (error) {
293
- const message = error.message;
294
- console.error(`ui-experience: failed to save the switch state: ${message}`);
295
- publish({ problem: `${t("palette.persist-failed")}: ${message}` });
296
- } finally {
297
- writing = false;
298
- publish({ saving: false });
299
- }
300
- };
301
- const savePaths = async (colors, tokens) => {
302
- if (disposed || writing) return;
303
- writing = true;
304
- publish({ saving: true, problem: "", notice: "" });
305
- try {
306
- const saved = unwrap(await gateway.setPaths({ colorsPath: colors, tokensPath: tokens }));
307
- if (disposed) return;
308
- const probe = unwrap(await gateway.poll({}));
309
- if (disposed) return;
310
- known = probe.status === "ok" ? probe.version : void 0;
311
- publish({
312
- paths: saved.paths,
313
- notice: probe.status === "ok" ? t("palette.saved") : "",
314
- problem: probe.status === "ok" ? "" : probe.message
315
- });
316
- if (probe.status === "ok" && view.active && probe.tokens !== void 0) applyPalette(probe.tokens);
317
- } catch (error) {
318
- const message = error.message;
319
- console.error(`ui-experience: failed to save the palette paths: ${message}`);
320
- publish({ problem: `${t("palette.save-failed")}: ${message}` });
321
- } finally {
322
- writing = false;
323
- publish({ saving: false });
324
- }
325
- };
326
- function PaletteRow(props) {
327
- const tr = props.t ?? t;
328
- const [state, setState] = React.useState(view);
329
- const [open, setOpen] = React.useState(false);
330
- const [colors, setColors] = React.useState(view.paths.colors);
331
- const [tokens, setTokens] = React.useState(view.paths.tokens);
332
- React.useEffect(() => {
333
- const listener = () => {
334
- setState(view);
335
- };
336
- listeners.add(listener);
337
- return () => {
338
- listeners.delete(listener);
339
- };
340
- }, []);
341
- React.useEffect(() => {
342
- setColors(state.paths.colors);
343
- setTokens(state.paths.tokens);
344
- }, [state.paths]);
345
- const status = state.problem !== "" ? h("div", { className: "dsh-uix-error", role: "alert" }, state.problem) : state.notice !== "" ? h("div", { className: "dsh-uix-notice" }, state.notice) : null;
346
- const field = (label, value, placeholder, set) => h(
347
- "label",
348
- { className: "dsh-uix-field" },
349
- h("span", { className: "dsh-uix-label" }, label),
350
- h("input", {
351
- className: "dsh-uix-input",
352
- type: "text",
353
- value,
354
- placeholder,
355
- spellCheck: false,
356
- onChange: (event) => {
357
- set(event.target.value);
358
- }
359
- })
360
- );
361
- const editor = open ? h(
362
- "div",
363
- { className: "dsh-uix-paths" },
364
- field(tr("palette.colors-path"), colors, DEFAULT_COLORS_PATH, setColors),
365
- field(tr("palette.tokens-path"), tokens, DEFAULT_TOKENS_PATH, setTokens),
366
- h(
367
- "div",
368
- { className: "dsh-uix-actions" },
369
- h("button", {
370
- type: "button",
371
- className: "dsh-uix-button dsh-uix-button-primary",
372
- disabled: state.saving,
373
- onClick: () => {
374
- void savePaths(colors.trim(), tokens.trim());
375
- }
376
- }, tr("palette.save")),
377
- h("button", {
378
- type: "button",
379
- className: "dsh-uix-button",
380
- disabled: state.saving,
381
- onClick: () => {
382
- void savePaths(DEFAULT_COLORS_PATH, DEFAULT_TOKENS_PATH);
383
- }
384
- }, tr("palette.reset"))
385
- ),
386
- h("div", { className: "dsh-uix-hint" }, tr("palette.paths-hint"))
387
- ) : null;
388
- return h(
389
- "div",
390
- { className: "dsh-uix-row" },
391
- h(
392
- "div",
393
- { className: "dsh-uix-head" },
394
- h(
395
- "div",
396
- { className: "dsh-uix-text" },
397
- h("div", { className: "dsh-uix-title" }, tr("palette.title")),
398
- h("div", { className: "dsh-uix-desc" }, tr("palette.description"))
399
- ),
400
- h("button", {
401
- type: "button",
402
- role: "switch",
403
- "aria-checked": state.active,
404
- "aria-label": tr("palette.title"),
405
- disabled: state.saving,
406
- className: "dsh-uix-switch",
407
- onClick: () => {
408
- void setActive(!state.active);
409
- }
410
- }, h("span", { className: "dsh-uix-switch-thumb", "aria-hidden": true }))
411
- ),
412
- status,
413
- h("button", {
414
- type: "button",
415
- className: "dsh-uix-disclosure",
416
- "aria-expanded": open,
417
- onClick: () => {
418
- setOpen(!open);
419
- }
420
- }, `${open ? "\u25BE" : "\u25B8"} ${tr("palette.paths")}`),
421
- editor
422
- );
423
- }
424
- ctx.effect(() => ctx.slots.inject("settings.general.item", () => ctx.slots.register({
425
- name: "settings.general.item",
426
- id: "ui-experience.palette",
427
- order: 10.5,
428
- locale: "ui-experience"
429
- }, PaletteRow)), "ui-experience: settings row");
430
- ctx.effect(() => ctx.timer.interval(() => {
431
- void poll();
432
- }, POLL_MS), "ui-experience: palette polling");
433
- ctx.effect(() => {
434
- return () => {
435
- disposed = true;
436
- teardown();
437
- };
438
- }, "ui-experience: palette override");
439
- void poll();
440
- }
441
- return {
442
- inject: ["slots", "locale", "timer", "theme", "remote"],
443
- async apply(ctx) {
444
- const theme = ctx.get("theme");
445
- if (theme === void 0) {
446
- console.error("ui-experience: ctx.theme is unavailable; the bundle cannot apply palettes");
447
- return;
448
- }
449
- const unmount = await ctx.remote.$mount(uiExperienceRemoteContribution(zod));
450
- ctx.effect(() => () => {
451
- unmount().catch((error) => {
452
- console.error(`ui-experience: remote unmount failed: ${error.message}`);
453
- });
454
- }, "ui-experience: remote namespace");
455
- const gateway = ctx.get(`remote.${REMOTE_SERVICE}`);
456
- if (gateway === void 0) {
457
- console.error("ui-experience: the remote namespace was not installed after mounting the contribution");
458
- return;
459
- }
460
- run(theme, ctx, gateway);
461
- }
462
- };
463
- }
464
- });
465
- }
466
- registerClient();
467
- })();
1
+ "use strict";
2
+ (() => {
3
+ // src/shared.ts
4
+ var REMOTE_SERVICE = "uiExperience";
5
+ var BOOT_ATTRIBUTE = "data-dsh-uix-matugen";
6
+ var MOTION_ATTRIBUTE = "data-dsh-uix-motion";
7
+ var DEFAULT_COLORS_PATH = "~/.local/state/quickshell/user/generated/colors.json";
8
+ var DEFAULT_TOKENS_PATH = "~/.local/state/quickshell/user/generated/dsh-tokens.json";
9
+ var MOTION_OFF = {
10
+ mode: "off",
11
+ durationScale: 1,
12
+ spatialSplit: true,
13
+ quantize: false,
14
+ reduced: "system"
15
+ };
16
+
17
+ // src/motion-plan.ts
18
+ var SPATIAL_PROPERTIES = /* @__PURE__ */ new Set([
19
+ "transform",
20
+ "translate",
21
+ "rotate",
22
+ "scale",
23
+ "offset-distance",
24
+ "offset-path",
25
+ "left",
26
+ "right",
27
+ "top",
28
+ "bottom",
29
+ "inset",
30
+ "inset-inline",
31
+ "inset-block",
32
+ "inset-inline-start",
33
+ "inset-inline-end",
34
+ "inset-block-start",
35
+ "inset-block-end",
36
+ "width",
37
+ "height",
38
+ "min-width",
39
+ "min-height",
40
+ "max-width",
41
+ "max-height",
42
+ "inline-size",
43
+ "block-size",
44
+ "min-inline-size",
45
+ "min-block-size",
46
+ "max-inline-size",
47
+ "max-block-size",
48
+ "flex-basis",
49
+ "margin",
50
+ "margin-top",
51
+ "margin-right",
52
+ "margin-bottom",
53
+ "margin-left",
54
+ "margin-inline",
55
+ "margin-block",
56
+ "margin-inline-start",
57
+ "margin-inline-end",
58
+ "padding",
59
+ "padding-top",
60
+ "padding-right",
61
+ "padding-bottom",
62
+ "padding-left",
63
+ "padding-inline",
64
+ "padding-block",
65
+ "padding-inline-start",
66
+ "padding-inline-end",
67
+ "gap",
68
+ "row-gap",
69
+ "column-gap",
70
+ "grid-template-columns",
71
+ "grid-template-rows",
72
+ "grid-auto-columns",
73
+ "grid-auto-rows",
74
+ "border-radius",
75
+ "border-top-left-radius",
76
+ "border-top-right-radius",
77
+ "border-bottom-left-radius",
78
+ "border-bottom-right-radius"
79
+ ]);
80
+ var CURVE_M3_SPATIAL = "cubic-bezier(0.2, 0, 0, 1)";
81
+ var CURVE_M3_EFFECTS = "cubic-bezier(0.2, 0, 0, 1)";
82
+ var CURVE_M3E_SPATIAL_FAST = "cubic-bezier(0.42, 1.67, 0.21, 0.9)";
83
+ var CURVE_M3E_SPATIAL = "cubic-bezier(0.38, 1.21, 0.22, 1)";
84
+ var CURVE_M3E_EFFECTS = "cubic-bezier(0.34, 0.8, 0.34, 1)";
85
+ var FAST_SPATIAL_MAX_MS = 200;
86
+ var MATERIAL_DURATIONS_MS = [
87
+ 50,
88
+ 100,
89
+ 150,
90
+ 200,
91
+ 250,
92
+ 300,
93
+ 350,
94
+ 400,
95
+ 450,
96
+ 500,
97
+ 550,
98
+ 600,
99
+ 700,
100
+ 800,
101
+ 900,
102
+ 1e3
103
+ ];
104
+ function splitList(value) {
105
+ if (value.trim() === "") return [];
106
+ const out = [];
107
+ let depth = 0;
108
+ let start = 0;
109
+ for (let index = 0; index < value.length; index += 1) {
110
+ const character = value[index];
111
+ if (character === "(") depth += 1;
112
+ else if (character === ")") depth = Math.max(0, depth - 1);
113
+ else if (character === "," && depth === 0) {
114
+ out.push(value.slice(start, index).trim());
115
+ start = index + 1;
116
+ }
117
+ }
118
+ out.push(value.slice(start).trim());
119
+ return out;
120
+ }
121
+ function entryAt(entries, index) {
122
+ if (entries.length === 0) return void 0;
123
+ return entries[index % entries.length];
124
+ }
125
+ var DURATION = /^([0-9]*\.?[0-9]+)(ms|s)$/;
126
+ function parseDurationMs(value) {
127
+ const match = DURATION.exec(value.trim());
128
+ if (match === null) return void 0;
129
+ const amount = Number(match[1]);
130
+ if (!Number.isFinite(amount) || amount < 0) return void 0;
131
+ return match[2] === "s" ? amount * 1e3 : amount;
132
+ }
133
+ function quantizeMs(ms) {
134
+ let best = MATERIAL_DURATIONS_MS[0] ?? 50;
135
+ let bestDistance = Number.POSITIVE_INFINITY;
136
+ for (const step of MATERIAL_DURATIONS_MS) {
137
+ const distance = Math.abs(step - ms);
138
+ if (distance < bestDistance) {
139
+ bestDistance = distance;
140
+ best = step;
141
+ }
142
+ }
143
+ return best;
144
+ }
145
+ var MAX_DURATION_MS = 1e4;
146
+ function retime(value, scale, quantize) {
147
+ const original = parseDurationMs(value);
148
+ if (original === void 0 || original === 0) return void 0;
149
+ const scaled = Math.round(original * scale);
150
+ const target = Math.min(MAX_DURATION_MS, Math.max(1, quantize ? quantizeMs(scaled) : scaled));
151
+ if (target === original) return void 0;
152
+ return `${target}ms`;
153
+ }
154
+ function isDiscrete(value) {
155
+ return /^(?:steps?\(|step-start$|step-end$)/.test(value.trim());
156
+ }
157
+ function classify(property) {
158
+ const name = property.trim().toLowerCase();
159
+ return SPATIAL_PROPERTIES.has(name) ? "spatial" : "effects";
160
+ }
161
+ function classifyAll(properties) {
162
+ for (const property of properties) {
163
+ if (classify(property) === "spatial") return "spatial";
164
+ }
165
+ return "effects";
166
+ }
167
+ function curveFor(mode, domain, spatialSplit, originalMs) {
168
+ if (mode === "m3") {
169
+ return domain === "spatial" ? CURVE_M3_SPATIAL : CURVE_M3_EFFECTS;
170
+ }
171
+ if (mode === "m3e" && spatialSplit && domain === "spatial") {
172
+ return originalMs !== void 0 && originalMs <= FAST_SPATIAL_MAX_MS ? CURVE_M3E_SPATIAL_FAST : CURVE_M3E_SPATIAL;
173
+ }
174
+ return CURVE_M3E_EFFECTS;
175
+ }
176
+
177
+ // src/motion.ts
178
+ var TRANSITIONS = {
179
+ selector: "transition-property",
180
+ duration: "transition-duration",
181
+ timing: "transition-timing-function"
182
+ };
183
+ var ANIMATIONS = {
184
+ selector: "animation-name",
185
+ duration: "animation-duration",
186
+ timing: "animation-timing-function",
187
+ iterations: "animation-iteration-count"
188
+ };
189
+ var DURATION_TOKENS = [
190
+ ["--ds-transition-duration", 200],
191
+ ["--ds-transition-duration-fast", 100],
192
+ ["--ds-transition-duration-slow", 300]
193
+ ];
194
+ function createMotion() {
195
+ const patches = /* @__PURE__ */ new Map();
196
+ const unreadable = /* @__PURE__ */ new Set();
197
+ const media = window.matchMedia("(prefers-reduced-motion: reduce)");
198
+ let settings;
199
+ let reducedSheet;
200
+ let reducedActive = false;
201
+ let observer;
202
+ let mediaListener;
203
+ let refreshPending = false;
204
+ function walk(rules, visit) {
205
+ for (let index = 0; index < rules.length; index += 1) {
206
+ const rule = rules[index];
207
+ if (rule === void 0) continue;
208
+ visit(rule);
209
+ if (rule.cssRules !== void 0) walk(rule.cssRules, visit);
210
+ }
211
+ }
212
+ function readableSheets() {
213
+ const out = [];
214
+ const collect = (sheet) => {
215
+ if (sheet === void 0) return;
216
+ try {
217
+ const rules = sheet.cssRules;
218
+ if (rules === void 0) return;
219
+ out.push(sheet);
220
+ } catch (error) {
221
+ const key = sheet.href ?? "(inline)";
222
+ if (unreadable.has(key)) return;
223
+ unreadable.add(key);
224
+ console.error(`ui-experience: motion cannot read stylesheet ${key}: ${error.message}`);
225
+ }
226
+ };
227
+ for (let index = 0; index < document.styleSheets.length; index += 1) {
228
+ collect(document.styleSheets[index]);
229
+ }
230
+ for (const sheet of document.adoptedStyleSheets ?? []) collect(sheet);
231
+ return out;
232
+ }
233
+ function keyframeProperties(sheets) {
234
+ const frames = /* @__PURE__ */ new Map();
235
+ for (const sheet of sheets) {
236
+ walk(sheet.cssRules, (rule) => {
237
+ if (rule.name === void 0 || rule.cssRules === void 0) return;
238
+ const properties = frames.get(rule.name) ?? [];
239
+ walk(rule.cssRules, (frame) => {
240
+ const declaration = frame.style;
241
+ if (declaration === void 0) return;
242
+ for (let index = 0; index < declaration.length; index += 1) {
243
+ const name = declaration.item(index);
244
+ if (name !== "" && !properties.includes(name)) properties.push(name);
245
+ }
246
+ });
247
+ frames.set(rule.name, properties);
248
+ });
249
+ }
250
+ return frames;
251
+ }
252
+ function rewriteFamily(declaration, family, frames, active) {
253
+ const selectors = splitList(declaration.getPropertyValue(family.selector));
254
+ const durations = splitList(declaration.getPropertyValue(family.duration));
255
+ const timings = splitList(declaration.getPropertyValue(family.timing));
256
+ const iterations = family.iterations === void 0 ? [] : splitList(declaration.getPropertyValue(family.iterations));
257
+ if (selectors.length === 0 && durations.length === 0 && timings.length === 0) return false;
258
+ const count = Math.max(selectors.length, durations.length, timings.length, 1);
259
+ const nextTimings = [];
260
+ const nextDurations = [];
261
+ let timingChanged = false;
262
+ let durationChanged = false;
263
+ for (let index = 0; index < count; index += 1) {
264
+ const selector = entryAt(selectors, index) ?? "all";
265
+ const originalTiming = entryAt(timings, index);
266
+ const originalDuration = entryAt(durations, index);
267
+ const originalMs = originalDuration === void 0 ? void 0 : parseDurationMs(originalDuration);
268
+ const infinite = entryAt(iterations, index) === "infinite";
269
+ if (selector === "none") {
270
+ if (originalTiming !== void 0) nextTimings.push(originalTiming);
271
+ if (originalDuration !== void 0) nextDurations.push(originalDuration);
272
+ continue;
273
+ }
274
+ const keepTiming = originalTiming !== void 0 && (isDiscrete(originalTiming) || infinite && originalTiming === "linear" || originalMs === 0);
275
+ const domain = infinite ? "effects" : family.selector === "transition-property" ? classify(selector) : classifyAll(frames.get(selector) ?? ["opacity"]);
276
+ const curve = keepTiming && originalTiming !== void 0 ? originalTiming : curveFor(active.mode, domain, active.spatialSplit, originalMs);
277
+ nextTimings.push(curve);
278
+ if (curve !== originalTiming) timingChanged = true;
279
+ if (originalDuration === void 0) continue;
280
+ const retimed = infinite ? void 0 : retime(originalDuration, active.durationScale, active.quantize);
281
+ nextDurations.push(retimed ?? originalDuration);
282
+ if (retimed !== void 0) durationChanged = true;
283
+ }
284
+ let changed = false;
285
+ if (timingChanged) {
286
+ declaration.setProperty(family.timing, nextTimings.join(", "));
287
+ changed = true;
288
+ }
289
+ if (durationChanged && nextDurations.length === count) {
290
+ declaration.setProperty(family.duration, nextDurations.join(", "));
291
+ changed = true;
292
+ }
293
+ return changed;
294
+ }
295
+ function scan() {
296
+ const active = settings;
297
+ if (active === void 0 || active.mode === "off") return;
298
+ const sheets = readableSheets();
299
+ const frames = keyframeProperties(sheets);
300
+ for (const sheet of sheets) {
301
+ walk(sheet.cssRules, (rule) => {
302
+ const declaration = rule.style;
303
+ if (declaration === void 0 || patches.has(rule)) return;
304
+ const before = declaration.cssText;
305
+ const touched = rewriteFamily(declaration, TRANSITIONS, frames, active) || rewriteFamily(declaration, ANIMATIONS, frames, active);
306
+ if (!touched) return;
307
+ patches.set(rule, { declaration, before, after: declaration.cssText });
308
+ });
309
+ }
310
+ }
311
+ function applyDurationTokens() {
312
+ const active = settings;
313
+ if (active === void 0 || active.durationScale === 1) return;
314
+ for (const [token, base] of DURATION_TOKENS) {
315
+ document.documentElement.style.setProperty(token, `${Math.round(base * active.durationScale)}ms`);
316
+ }
317
+ }
318
+ function clearDurationTokens() {
319
+ for (const [token] of DURATION_TOKENS) document.documentElement.style.removeProperty(token);
320
+ }
321
+ function wantsReduced() {
322
+ const active = settings;
323
+ if (active === void 0) return false;
324
+ if (active.reduced === "always") return true;
325
+ return active.reduced === "system" && media.matches;
326
+ }
327
+ function syncReduced() {
328
+ const want = wantsReduced();
329
+ if (want === reducedActive) return;
330
+ reducedActive = want;
331
+ reducedSheet?.remove();
332
+ reducedSheet = void 0;
333
+ if (!want) return;
334
+ const sheet = document.createElement("style");
335
+ sheet.textContent = "*,*::before,*::after{transition-duration:1ms !important;animation-duration:1ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important}";
336
+ document.head.appendChild(sheet);
337
+ reducedSheet = sheet;
338
+ }
339
+ function refresh() {
340
+ if (refreshPending) return;
341
+ refreshPending = true;
342
+ queueMicrotask(() => {
343
+ refreshPending = false;
344
+ const active = settings;
345
+ if (active === void 0 || active.mode === "off" || reducedActive) return;
346
+ scan();
347
+ });
348
+ }
349
+ function observeDocument() {
350
+ const instance = new MutationObserver(() => {
351
+ refresh();
352
+ });
353
+ instance.observe(document.head, { childList: true, subtree: true });
354
+ return { disconnect: () => {
355
+ instance.disconnect();
356
+ } };
357
+ }
358
+ function unwatchPreference() {
359
+ if (mediaListener === void 0) return;
360
+ media.removeEventListener?.("change", mediaListener);
361
+ mediaListener = void 0;
362
+ }
363
+ function watchPreference() {
364
+ if (mediaListener !== void 0) return;
365
+ mediaListener = () => {
366
+ syncReduced();
367
+ };
368
+ media.addEventListener?.("change", mediaListener);
369
+ }
370
+ function restore() {
371
+ for (const patch of patches.values()) {
372
+ if (patch.declaration.cssText !== patch.after) {
373
+ console.error("ui-experience: motion restore overwrote a declaration that changed after this layer wrote it");
374
+ }
375
+ patch.declaration.cssText = patch.before;
376
+ }
377
+ patches.clear();
378
+ clearDurationTokens();
379
+ reducedSheet?.remove();
380
+ reducedSheet = void 0;
381
+ reducedActive = false;
382
+ unwatchPreference();
383
+ document.documentElement.removeAttribute(MOTION_ATTRIBUTE);
384
+ }
385
+ return {
386
+ apply(next) {
387
+ restore();
388
+ settings = next;
389
+ if (next.mode === "off") return;
390
+ watchPreference();
391
+ syncReduced();
392
+ if (reducedActive) {
393
+ document.documentElement.setAttribute(MOTION_ATTRIBUTE, next.mode);
394
+ return;
395
+ }
396
+ applyDurationTokens();
397
+ scan();
398
+ observer ??= observeDocument();
399
+ document.documentElement.setAttribute(MOTION_ATTRIBUTE, next.mode);
400
+ },
401
+ refresh,
402
+ restore,
403
+ dispose() {
404
+ restore();
405
+ observer?.disconnect();
406
+ observer = void 0;
407
+ settings = void 0;
408
+ }
409
+ };
410
+ }
411
+
412
+ // src/styles.ts
413
+ var ROW_CSS = `
414
+ /* Row chrome: AppearanceRow.module.css .group \u2014 16/0 padding, hairline
415
+ separator, column gap 8 (the section column strips the last separator). */
416
+ .dsh-uix-row{display:flex;flex-direction:column;gap:8px;padding:16px 0;border-bottom:0.5px solid var(--dsw-alias-border-l2)}
417
+ .dsh-uix-head{display:flex;align-items:center;justify-content:space-between;gap:24px}
418
+ .dsh-uix-text{display:flex;flex-direction:column;gap:4px;min-width:0}
419
+
420
+ /* Title and description: FontSizeRow.module.css .title / .desc \u2014
421
+ 14/400/22 label-primary and 12/400/18 label-tertiary. */
422
+ .dsh-uix-title{font-size:14px;font-weight:400;line-height:22px;color:var(--dsw-alias-label-primary)}
423
+ .dsh-uix-desc{font-size:12px;font-weight:400;line-height:18px;color:var(--dsw-alias-label-tertiary)}
424
+
425
+ /* Feedback lines: ConfigField.module.css .field p \u2014 12px, no margin. The
426
+ success line reuses the same metrics with the success token. */
427
+ .dsh-uix-problem{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
428
+ .dsh-uix-notice{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-success-primary)}
429
+
430
+ /* Disclosure target: sized like the field label it introduces, with the
431
+ shared hover wash and focus ring instead of a colour-only affordance. */
432
+ /* Disclosure: FontSizeRow.module.css .expander \u2014 a 24px icon button whose
433
+ chevron rotates 180deg while expanded, beside the label text. */
434
+ .dsh-uix-disclosure{display:inline-flex;align-items:center;gap:4px;align-self:flex-start;margin-top:4px;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:1.5;cursor:pointer}
435
+ .dsh-uix-disclosure:hover{color:var(--dsw-alias-label-secondary)}
436
+ .dsh-uix-disclosure:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:var(--dsw-radius-xs)}
437
+ .dsh-uix-disclosure-icon{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:var(--dsw-radius-xs)}
438
+ .dsh-uix-disclosure:hover .dsh-uix-disclosure-icon{background:var(--dsw-alias-interactive-bg-hover)}
439
+ .dsh-uix-disclosure-icon svg{transition:transform 150ms ease}
440
+ .dsh-uix-disclosure[aria-expanded=true] .dsh-uix-disclosure-icon svg{transform:rotate(180deg)}
441
+
442
+ /* Path fields: ConfigField.module.css .field, .field + .field, .head label,
443
+ and .field input. The input is sized by padding, not a fixed height. */
444
+ .dsh-uix-form{display:flex;flex-direction:column}
445
+ .dsh-uix-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}
446
+ .dsh-uix-field+.dsh-uix-field{border-top:0.5px solid var(--dsw-alias-border-l2)}
447
+ .dsh-uix-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
448
+ /* Input primitive: Input.module.css .wrap / .input / placeholder \u2014 the
449
+ wrapper owns the 32px box and the border, the inner input is transparent.
450
+ The same page's font-family fields render through this primitive. */
451
+ .dsh-uix-input-wrap{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;width:100%;height:32px;padding:0 8px;border:0.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1)}
452
+ .dsh-uix-input-wrap:focus-within{border-color:var(--dsw-alias-state-business-primary)}
453
+ .dsh-uix-input{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
454
+ .dsh-uix-input::placeholder{color:var(--dsw-alias-label-dimmed)}
455
+ .dsh-uix-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
456
+
457
+ /* Form footer and its save action: the configuration form's footer and save
458
+ button (13px, 5/14 padding, radius md, inverted fill). */
459
+ .dsh-uix-footer{display:flex;align-items:center;gap:8px;padding-top:16px}
460
+ .dsh-uix-save{appearance:none;border:1px solid transparent;border-radius:var(--dsw-radius-md);padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;cursor:pointer;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
461
+ .dsh-uix-save:disabled{cursor:not-allowed;opacity:.4}
462
+
463
+ /* Secondary action: primitives Button.module.css .outline \u2014 inset hairline
464
+ that keeps its width independent of the border, plus the shared wash. */
465
+ .dsh-uix-reset{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border:0.5px solid transparent;border-radius:var(--dsw-radius-md);padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary);box-shadow:inset 0 0 0 var(--dsh-hairline-width,0.5px) var(--dsh-button-outline-color,var(--dsw-alias-border-l3))}
466
+ .dsh-uix-reset:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
467
+ .dsh-uix-reset:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}
468
+ .dsh-uix-reset:disabled{cursor:not-allowed;opacity:.4}
469
+ .dsh-uix-save:focus-visible,.dsh-uix-reset:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-alias-state-business-primary);outline-offset:2px}
470
+
471
+ /* Switch: Switch.module.css \u2014 36x20 track, 2px inset, 16px thumb, keyed off
472
+ aria-checked so the visual state cannot disagree with assistive technology.
473
+ corner-shape opts the capsule and the thumb out of the superellipse. */
474
+ .dsh-uix-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer}
475
+ .dsh-uix-switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}
476
+ .dsh-uix-switch:disabled{cursor:default;opacity:.5}
477
+ .dsh-uix-switch:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
478
+ .dsh-uix-switch-thumb{display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
479
+ .dsh-uix-switch[aria-checked=false] .dsh-uix-switch-thumb{background:var(--dsw-alias-switch-thumb)}
480
+ .dsh-uix-switch[aria-checked=true] .dsh-uix-switch-thumb{transform:translate(16px)}
481
+
482
+ /* A labelled control line: the caption column and the control that follows it,
483
+ mirroring the settings form's label/control pairing. */
484
+ .dsh-uix-choice{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
485
+ .dsh-uix-choice-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
486
+
487
+ /* Segmented control: SegmentedControl.module.css .control / .indicator / .tab \u2014
488
+ a hover-wash track holding one sliding pill. The indicator is placed
489
+ arithmetically from the segment count and index carried as custom properties,
490
+ so nothing measures the DOM. */
491
+ .dsh-uix-segment{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;padding:4px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover)}
492
+ .dsh-uix-segment-indicator{position:absolute;top:4px;left:4px;width:calc((100% - 8px - 2px * (var(--dsh-segment-count) - 1)) / var(--dsh-segment-count));height:calc(100% - 8px);border:0;border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);transform:translateX(calc(var(--dsh-segment-index) * (100% + 2px)));transition:transform 160ms ease;pointer-events:none}
493
+ .dsh-uix-segment-tab{box-sizing:border-box;position:relative;z-index:1;height:28px;padding:0 16px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;font-weight:500;white-space:nowrap;cursor:pointer;transition:color 120ms ease}
494
+ .dsh-uix-segment-tab[aria-selected=true]{color:var(--dsw-alias-label-primary)}
495
+ .dsh-uix-segment-tab:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
496
+ .dsh-uix-segment-tab:disabled{cursor:default;opacity:.4}
497
+ .dsh-uix-segment-tab:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}
498
+
499
+ /* Checkbox: Checkbox.module.css \u2014 a native input so the platform draws and keys
500
+ it, tinted with the brand accent, with the explanation stacked underneath. */
501
+ .dsh-uix-check{display:flex;align-items:flex-start;gap:6px;padding:10px 0;font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary);cursor:pointer}
502
+ .dsh-uix-check+.dsh-uix-check{border-top:0.5px solid var(--dsw-alias-border-l2)}
503
+ .dsh-uix-check input{flex:0 0 auto;width:16px;height:16px;margin:2px 0 0;accent-color:var(--dsw-alias-brand-primary);cursor:inherit}
504
+ .dsh-uix-check input:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
505
+ .dsh-uix-check:has(input:disabled){cursor:default;opacity:.5}
506
+ .dsh-uix-check-text{display:flex;flex-direction:column;gap:2px;min-width:0}
507
+ .dsh-uix-check-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
508
+ .dsh-uix-check-hint{font-size:12px;line-height:1.5;font-weight:400;color:var(--dsw-alias-label-tertiary)}
509
+ `;
510
+
511
+ // src/client.ts
512
+ var REDUCED_QUERY = "(prefers-reduced-motion: reduce)";
513
+ function queryReduced() {
514
+ return window.matchMedia(REDUCED_QUERY).matches;
515
+ }
516
+ function watchReduced(report) {
517
+ const media = window.matchMedia(REDUCED_QUERY);
518
+ const listener = () => {
519
+ report(media.matches);
520
+ };
521
+ report(media.matches);
522
+ media.addEventListener?.("change", listener);
523
+ return () => {
524
+ media.removeEventListener?.("change", listener);
525
+ };
526
+ }
527
+ var SOURCE = "@dsh-plugin/dsh-ui-experience";
528
+ var POLL_MS = 2e3;
529
+ var IDLE_TICKS = 15;
530
+ var BORDER_WIDTH = /^(?:0|[0-9]+(?:\.[0-9]+)?(?:px|rem|em))$/;
531
+ function uiExperienceRemoteContribution(zod) {
532
+ const codec = (name, schema) => ({
533
+ mode: "strict",
534
+ typeSymbol: `${SOURCE}/shared#${name}`,
535
+ create: schema
536
+ });
537
+ const descriptor = (method, shape, typeName) => ({
538
+ id: `${SOURCE}#${REMOTE_SERVICE}/${method}`,
539
+ service: REMOTE_SERVICE,
540
+ namespace: REMOTE_SERVICE,
541
+ method,
542
+ invocation: { kind: "direct" },
543
+ parameters: [{
544
+ name: "args",
545
+ wire: "args",
546
+ source: "json",
547
+ codec: codec(typeName, () => zod.object(shape())),
548
+ strictCodec: codec(typeName, () => zod.strictObject(shape()))
549
+ }],
550
+ result: codec(`${method}Result`, () => zod.object({}))
551
+ });
552
+ return {
553
+ package: SOURCE,
554
+ descriptors: [
555
+ descriptor("poll", () => ({ known: zod.string().optional() }), "PollArgs"),
556
+ descriptor("setActive", () => ({ active: zod.boolean().optional() }), "SetActiveArgs"),
557
+ descriptor("setPaths", () => ({ colorsPath: zod.string().optional(), tokensPath: zod.string().optional() }), "SetPathsArgs"),
558
+ descriptor("setMotion", () => ({
559
+ mode: zod.string().optional(),
560
+ durationScale: zod.number().optional(),
561
+ spatialSplit: zod.boolean().optional(),
562
+ quantize: zod.boolean().optional(),
563
+ reduced: zod.string().optional()
564
+ }), "SetMotionArgs")
565
+ ]
566
+ };
567
+ }
568
+ var DICTS = {
569
+ zh: {
570
+ "palette.title": "Matugen \u914D\u8272\u8DDF\u968F",
571
+ "palette.description": "\u5C06\u7CFB\u7EDF matugen \u751F\u6210\u7684 Material You \u914D\u8272\u53E0\u52A0\u5728\u5F53\u524D\u4E3B\u9898\u4E0A\uFF0C\u914D\u8272\u6587\u4EF6\u91CD\u65B0\u751F\u6210\u540E\u81EA\u52A8\u8DDF\u968F",
572
+ "palette.persist-failed": "\u5F00\u5173\u72B6\u6001\u4FDD\u5B58\u5931\u8D25",
573
+ "palette.save-failed": "\u8DEF\u5F84\u4FDD\u5B58\u5931\u8D25",
574
+ "palette.saved": "\u5DF2\u4FDD\u5B58",
575
+ "palette.paths": "\u914D\u8272\u6587\u4EF6\u8DEF\u5F84",
576
+ "palette.colors-path": "matugen \u989C\u8272\u6587\u4EF6",
577
+ "palette.tokens-path": "DSH \u4EE4\u724C\u6587\u4EF6\uFF08\u5B58\u5728\u65F6\u4F18\u5148\uFF09",
578
+ "palette.save": "\u4FDD\u5B58",
579
+ "palette.reset": "\u6062\u590D\u9ED8\u8BA4",
580
+ "palette.paths-hint": "\u652F\u6301\u4EE5 ~ \u5F00\u5934\u7684\u8DEF\u5F84\uFF0C\u6216\u7EDD\u5BF9\u8DEF\u5F84\u3002\u989C\u8272\u6587\u4EF6\u63D0\u4F9B\u5B8C\u6574\u6620\u5C04\uFF0C\u4EE4\u724C\u6587\u4EF6\u91CC\u5217\u51FA\u7684 token \u9010\u9879\u8986\u76D6\u3002",
581
+ "motion.title": "M3 \u52A8\u6548",
582
+ "motion.description": "\u6309 Material 3 \u66F2\u7EBF\u6539\u5199\u6574\u4E2A\u754C\u9762\u7684\u8FC7\u6E21\u4E0E\u5173\u952E\u5E27\u52A8\u753B\uFF0C\u539F\u5730\u6539\u5199\u5165\u5DF2\u751F\u6548\u7684\u6837\u5F0F\u89C4\u5219",
583
+ "motion.mode": "\u66F2\u7EBF\u65CF",
584
+ "motion.mode-off": "\u5173\u95ED",
585
+ "motion.mode-m3": "M3",
586
+ "motion.mode-m3e": "M3E",
587
+ "motion.duration": "\u65F6\u957F\u500D\u7387",
588
+ "motion.options": "\u9AD8\u7EA7\u9009\u9879",
589
+ "motion.spatial-split": "\u4F4D\u79FB\u4E0E\u6548\u679C\u4F7F\u7528\u4E0D\u540C\u66F2\u7EBF",
590
+ "motion.spatial-split-hint": "M3E \u7684\u4F4D\u79FB\u66F2\u7EBF\u5E26\u6709\u7EA6 9% \u7684\u8FC7\u51B2\uFF0C\u6548\u679C\u66F2\u7EBF\u6CA1\u6709\u3002\u5173\u95ED\u540E\u5168\u90E8\u4F7F\u7528\u6548\u679C\u66F2\u7EBF\u3002",
591
+ "motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
592
+ "motion.quantize-hint": "\u628A\u6539\u5199\u540E\u7684\u65F6\u957F\u5438\u9644\u5230 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000ms\u3002",
593
+ "motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
594
+ "motion.reduced-hint": "\u5F00\u542F\u540E\u5FFD\u7565\u7CFB\u7EDF\u7684\u300C\u51CF\u5F31\u52A8\u6001\u6548\u679C\u300D\u504F\u597D\uFF0C\u4E00\u5F8B\u628A\u65F6\u957F\u538B\u5230 1ms\u3002\u5173\u95ED\u65F6\u8DDF\u968F\u7CFB\u7EDF\u3002",
595
+ "motion.system-reduced": "\u7CFB\u7EDF\u5DF2\u8BF7\u6C42\u51CF\u5F31\u52A8\u6548\uFF0C\u672C\u9875\u52A8\u753B\u6B63\u5728\u88AB\u538B\u5236\u3002",
596
+ "motion.save-failed": "\u52A8\u6548\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25",
597
+ "motion.inert": "\u66F2\u7EBF\u65CF\u4E3A\u300C\u5173\u95ED\u300D\uFF0C\u4EE5\u4E0B\u9009\u9879\u4E0D\u4F1A\u751F\u6548\u3002"
598
+ },
599
+ en: {
600
+ "palette.title": "Matugen palette sync",
601
+ "palette.description": "Overlay the system matugen Material You palette on the current theme; follows the file when matugen regenerates it",
602
+ "palette.persist-failed": "Failed to save the switch state",
603
+ "palette.save-failed": "Failed to save the paths",
604
+ "palette.saved": "Saved",
605
+ "palette.paths": "Palette file paths",
606
+ "palette.colors-path": "matugen colors file",
607
+ "palette.tokens-path": "DSH token file (preferred when present)",
608
+ "palette.save": "Save",
609
+ "palette.reset": "Restore defaults",
610
+ "palette.paths-hint": "Paths may start with ~, or be absolute. The colors file supplies the full mapping; tokens listed in the token file override it item by item.",
611
+ "motion.title": "M3 motion",
612
+ "motion.description": "Rewrite every transition and keyframe animation in the interface onto Material 3 curves, in place on the rules already in the cascade",
613
+ "motion.mode": "Curve family",
614
+ "motion.mode-off": "Off",
615
+ "motion.mode-m3": "M3",
616
+ "motion.mode-m3e": "M3E",
617
+ "motion.duration": "Duration scale",
618
+ "motion.options": "Advanced options",
619
+ "motion.spatial-split": "Spatial and effects take different curves",
620
+ "motion.spatial-split-hint": "The M3E spatial curve overshoots by about 9%, the effects curve does not. Turning this off uses the effects curve everywhere.",
621
+ "motion.quantize": "Snap durations to the Material ladder",
622
+ "motion.quantize-hint": "Pull rewritten durations onto 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000 ms.",
623
+ "motion.reduced": "Always reduce motion",
624
+ "motion.reduced-hint": "Ignore the system reduce-motion preference and force every duration to 1 ms. Off follows the system.",
625
+ "motion.system-reduced": "The system asked for reduced motion; this page is being held still.",
626
+ "motion.save-failed": "Failed to save the motion settings",
627
+ "motion.inert": "The curve family is Off, so these options have no effect."
628
+ }
629
+ };
630
+ function registerClient() {
631
+ window.__ModuleLoader__.load({
632
+ id: SOURCE,
633
+ factory(require2) {
634
+ const React = require2("react");
635
+ const zod = require2("zod");
636
+ const h = React.createElement;
637
+ function run(registry, ctx, gateway) {
638
+ const t = ctx.locale.bind("ui-experience");
639
+ ctx.effect(() => ctx.locale.register("ui-experience", DICTS), "ui-experience: dictionaries");
640
+ ctx.effect(() => {
641
+ const sheet = document.createElement("style");
642
+ sheet.textContent = ROW_CSS;
643
+ document.head.appendChild(sheet);
644
+ return () => {
645
+ sheet.remove();
646
+ };
647
+ }, "ui-experience: row styles");
648
+ let disposed = false;
649
+ let known;
650
+ let bootstrapped = false;
651
+ let writing = false;
652
+ let inflight = false;
653
+ let idleTick = 0;
654
+ let lastRpcProblem = "";
655
+ let lastPaletteProblem = "";
656
+ let paintedWidth = "";
657
+ let disposeOverride;
658
+ let borderSheet;
659
+ let view = {
660
+ active: false,
661
+ borderWidth: "0.5px",
662
+ paths: { colors: DEFAULT_COLORS_PATH, tokens: DEFAULT_TOKENS_PATH },
663
+ motion: { ...MOTION_OFF },
664
+ problem: "",
665
+ notice: "",
666
+ saving: false
667
+ };
668
+ const listeners = /* @__PURE__ */ new Set();
669
+ const motion = createMotion();
670
+ const publish = (patch) => {
671
+ view = { ...view, ...patch };
672
+ for (const listener of listeners) listener();
673
+ };
674
+ const toOverrides = (modes) => {
675
+ const out = {};
676
+ for (const [token, dark] of Object.entries(modes.dark)) {
677
+ const light = modes.light[token] ?? dark;
678
+ if (typeof light === "string" && light !== "") out[token] = { light, dark };
679
+ }
680
+ return out;
681
+ };
682
+ const applyMotion = (next) => {
683
+ if (disposed) return;
684
+ motion.apply(next);
685
+ };
686
+ const motionDiffers = (left, right) => left.mode !== right.mode || left.durationScale !== right.durationScale || left.spatialSplit !== right.spatialSplit || left.quantize !== right.quantize || left.reduced !== right.reduced;
687
+ const clearOverride = () => {
688
+ disposeOverride?.();
689
+ disposeOverride = void 0;
690
+ };
691
+ const applyPalette = (modes) => {
692
+ if (disposed) return;
693
+ clearOverride();
694
+ try {
695
+ disposeOverride = registry.overrideTokens(SOURCE, toOverrides(modes));
696
+ } catch (error) {
697
+ const message = `theme override rejected: ${error.message}`;
698
+ console.error(`ui-experience: ${message}`);
699
+ publish({ problem: message });
700
+ }
701
+ };
702
+ const applyBorder = (width) => {
703
+ if (disposed) return;
704
+ if (!BORDER_WIDTH.test(width)) {
705
+ const message = `borderWidth ${JSON.stringify(width)} is not a CSS length; borders left untouched`;
706
+ console.error(`ui-experience: ${message}`);
707
+ publish({ problem: message });
708
+ return;
709
+ }
710
+ if (width === paintedWidth) return;
711
+ paintedWidth = width;
712
+ borderSheet?.remove();
713
+ borderSheet = document.createElement("style");
714
+ borderSheet.textContent = `body :where(*:not([data-dsh-uix-keep-border])){border-width:${width} !important}`;
715
+ document.head.appendChild(borderSheet);
716
+ };
717
+ const clearBorder = () => {
718
+ borderSheet?.remove();
719
+ borderSheet = void 0;
720
+ paintedWidth = "";
721
+ };
722
+ const setBootAttribute = (on) => {
723
+ if (on) document.documentElement.setAttribute(BOOT_ATTRIBUTE, "");
724
+ else document.documentElement.removeAttribute(BOOT_ATTRIBUTE);
725
+ };
726
+ const teardown = () => {
727
+ clearOverride();
728
+ clearBorder();
729
+ setBootAttribute(false);
730
+ };
731
+ const unwrap = (raw) => {
732
+ const envelope = raw;
733
+ if (envelope.ok === false) throw new Error(envelope.error?.message ?? "remote call failed");
734
+ return envelope.value;
735
+ };
736
+ const adopt = (answer) => {
737
+ if (answer.paths.colors !== view.paths.colors || answer.paths.tokens !== view.paths.tokens || answer.borderWidth !== view.borderWidth) {
738
+ publish({ paths: answer.paths, borderWidth: answer.borderWidth });
739
+ }
740
+ if (motionDiffers(view.motion, answer.motion)) {
741
+ publish({ motion: answer.motion });
742
+ if (bootstrapped) applyMotion(answer.motion);
743
+ }
744
+ if (!bootstrapped) {
745
+ bootstrapped = true;
746
+ setBootAttribute(answer.active);
747
+ publish({ active: answer.active });
748
+ applyMotion(answer.motion);
749
+ } else if (!writing && view.active !== answer.active) {
750
+ publish({ active: answer.active });
751
+ if (answer.active) {
752
+ setBootAttribute(true);
753
+ if (answer.status === "ok" && answer.tokens !== void 0) applyPalette(answer.tokens);
754
+ applyBorder(answer.borderWidth);
755
+ } else {
756
+ teardown();
757
+ }
758
+ }
759
+ if (answer.status !== "ok") {
760
+ if (answer.message !== lastPaletteProblem) {
761
+ lastPaletteProblem = answer.message;
762
+ console.error(`ui-experience: palette unavailable: ${answer.message}`);
763
+ }
764
+ publish({ problem: answer.message });
765
+ return;
766
+ }
767
+ if (lastPaletteProblem !== "") {
768
+ lastPaletteProblem = "";
769
+ publish({ problem: "" });
770
+ }
771
+ if (answer.changed) {
772
+ known = answer.version;
773
+ if (view.active && answer.tokens !== void 0) applyPalette(answer.tokens);
774
+ }
775
+ if (view.active) applyBorder(answer.borderWidth);
776
+ };
777
+ const poll = async () => {
778
+ if (disposed || inflight) return;
779
+ if (bootstrapped && !view.active && view.motion.mode === "off") {
780
+ idleTick = (idleTick + 1) % IDLE_TICKS;
781
+ if (idleTick !== 0) return;
782
+ } else {
783
+ idleTick = 0;
784
+ }
785
+ inflight = true;
786
+ let answer;
787
+ try {
788
+ answer = unwrap(await gateway.poll({ known }));
789
+ } catch (error) {
790
+ const message = error.message;
791
+ if (message !== lastRpcProblem) {
792
+ lastRpcProblem = message;
793
+ console.error(`ui-experience: poll RPC failed: ${message}`);
794
+ }
795
+ return;
796
+ } finally {
797
+ inflight = false;
798
+ }
799
+ if (disposed) return;
800
+ lastRpcProblem = "";
801
+ adopt(answer);
802
+ };
803
+ const saveMotion = async (change) => {
804
+ if (disposed || writing) return;
805
+ writing = true;
806
+ publish({ saving: true, problem: "", notice: "" });
807
+ try {
808
+ const applied = unwrap(await gateway.setMotion(change));
809
+ if (disposed) return;
810
+ publish({ motion: applied, notice: t("palette.saved") });
811
+ applyMotion(applied);
812
+ } catch (error) {
813
+ const message = error.message;
814
+ console.error(`ui-experience: failed to save the motion settings: ${message}`);
815
+ publish({ problem: `${t("motion.save-failed")}: ${message}` });
816
+ } finally {
817
+ writing = false;
818
+ publish({ saving: false });
819
+ }
820
+ };
821
+ const setActive = async (next) => {
822
+ if (disposed || writing) return;
823
+ writing = true;
824
+ publish({ saving: true, problem: "", notice: "" });
825
+ try {
826
+ if (!next) {
827
+ await gateway.setActive({ active: false });
828
+ if (disposed) return;
829
+ publish({ active: false });
830
+ teardown();
831
+ return;
832
+ }
833
+ const answer = unwrap(await gateway.poll({}));
834
+ if (disposed) return;
835
+ if (answer.status !== "ok") {
836
+ publish({ problem: answer.message });
837
+ return;
838
+ }
839
+ const tokens = answer.tokens;
840
+ if (tokens === void 0) throw new Error("poll returned no tokens for an activation request");
841
+ await gateway.setActive({ active: true });
842
+ if (disposed) return;
843
+ known = answer.version;
844
+ bootstrapped = true;
845
+ setBootAttribute(true);
846
+ publish({ active: true, paths: answer.paths, notice: t("palette.saved") });
847
+ applyPalette(tokens);
848
+ applyBorder(answer.borderWidth);
849
+ } catch (error) {
850
+ const message = error.message;
851
+ console.error(`ui-experience: failed to save the switch state: ${message}`);
852
+ publish({ problem: `${t("palette.persist-failed")}: ${message}` });
853
+ } finally {
854
+ writing = false;
855
+ publish({ saving: false });
856
+ }
857
+ };
858
+ const savePaths = async (colors, tokens) => {
859
+ if (disposed || writing) return;
860
+ writing = true;
861
+ publish({ saving: true, problem: "", notice: "" });
862
+ try {
863
+ const saved = unwrap(await gateway.setPaths({ colorsPath: colors, tokensPath: tokens }));
864
+ if (disposed) return;
865
+ const probe = unwrap(await gateway.poll({}));
866
+ if (disposed) return;
867
+ known = probe.status === "ok" ? probe.version : void 0;
868
+ publish({
869
+ paths: saved.paths,
870
+ notice: probe.status === "ok" ? t("palette.saved") : "",
871
+ problem: probe.status === "ok" ? "" : probe.message
872
+ });
873
+ if (probe.status === "ok" && view.active && probe.tokens !== void 0) applyPalette(probe.tokens);
874
+ } catch (error) {
875
+ const message = error.message;
876
+ console.error(`ui-experience: failed to save the palette paths: ${message}`);
877
+ publish({ problem: `${t("palette.save-failed")}: ${message}` });
878
+ } finally {
879
+ writing = false;
880
+ publish({ saving: false });
881
+ }
882
+ };
883
+ const disclosure = (open, toggle, label) => h(
884
+ "button",
885
+ {
886
+ type: "button",
887
+ className: "dsh-uix-disclosure",
888
+ "aria-expanded": open,
889
+ onClick: toggle
890
+ },
891
+ h(
892
+ "span",
893
+ { className: "dsh-uix-disclosure-icon" },
894
+ h("svg", {
895
+ viewBox: "0 0 16 16",
896
+ width: 14,
897
+ height: 14,
898
+ fill: "none",
899
+ stroke: "currentColor",
900
+ strokeWidth: 1,
901
+ "aria-hidden": true
902
+ }, h("path", { d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6" }))
903
+ ),
904
+ h("span", null, label)
905
+ );
906
+ const WALK_KEYS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"];
907
+ const segmented = (id, value, segments, select, label, disabled) => {
908
+ const selected = Math.max(0, segments.findIndex((segment) => segment.value === value));
909
+ const walk = (key) => {
910
+ if (segments.length === 0) return;
911
+ const current = segments[selected];
912
+ if (current === void 0) return;
913
+ let next;
914
+ if (key === "Home") next = 0;
915
+ else if (key === "End") next = segments.length - 1;
916
+ else {
917
+ const step = key === "ArrowRight" || key === "ArrowDown" ? 1 : -1;
918
+ next = ((selected + step) % segments.length + segments.length) % segments.length;
919
+ }
920
+ const target = segments[next];
921
+ if (target !== void 0 && target.value !== value) select(target.value);
922
+ };
923
+ return h(
924
+ "div",
925
+ {
926
+ role: "tablist",
927
+ "aria-label": label,
928
+ className: "dsh-uix-segment",
929
+ style: {
930
+ "--dsh-segment-count": String(segments.length),
931
+ "--dsh-segment-index": String(selected)
932
+ }
933
+ },
934
+ h("span", { "aria-hidden": true, className: "dsh-uix-segment-indicator" }),
935
+ ...segments.map((segment) => h("button", {
936
+ key: segment.value,
937
+ id: `${id}-${segment.value}`,
938
+ type: "button",
939
+ role: "tab",
940
+ "aria-selected": segment.value === value,
941
+ tabIndex: segment.value === value ? 0 : -1,
942
+ disabled,
943
+ className: "dsh-uix-segment-tab",
944
+ onClick: () => {
945
+ if (segment.value !== value) select(segment.value);
946
+ },
947
+ onKeyDown: (event) => {
948
+ if (!WALK_KEYS.includes(event.key)) return;
949
+ event.preventDefault();
950
+ walk(event.key);
951
+ }
952
+ }, segment.label))
953
+ );
954
+ };
955
+ const checkbox = (id, checked, set, label, hint, disabled) => h(
956
+ "label",
957
+ { className: "dsh-uix-check", htmlFor: id },
958
+ h("input", {
959
+ id,
960
+ type: "checkbox",
961
+ checked,
962
+ disabled,
963
+ onChange: (event) => {
964
+ set(event.target.checked);
965
+ }
966
+ }),
967
+ h(
968
+ "span",
969
+ { className: "dsh-uix-check-text" },
970
+ h("span", { className: "dsh-uix-check-label" }, label),
971
+ hint === void 0 ? null : h("span", { className: "dsh-uix-check-hint" }, hint)
972
+ )
973
+ );
974
+ function PaletteRow(props) {
975
+ const tr = props.t ?? t;
976
+ const [state, setState] = React.useState(view);
977
+ const [open, setOpen] = React.useState(false);
978
+ const [colors, setColors] = React.useState(view.paths.colors);
979
+ const [tokens, setTokens] = React.useState(view.paths.tokens);
980
+ React.useEffect(() => {
981
+ const listener = () => {
982
+ setState(view);
983
+ };
984
+ listeners.add(listener);
985
+ return () => {
986
+ listeners.delete(listener);
987
+ };
988
+ }, []);
989
+ React.useEffect(() => {
990
+ setColors(state.paths.colors);
991
+ setTokens(state.paths.tokens);
992
+ }, [state.paths]);
993
+ const status = state.problem !== "" ? h("p", { className: "dsh-uix-problem", role: "alert" }, state.problem) : state.notice !== "" ? h("p", { className: "dsh-uix-notice" }, state.notice) : null;
994
+ const field = (label, value, placeholder, set, hint) => h(
995
+ "label",
996
+ { className: "dsh-uix-field" },
997
+ h("span", { className: "dsh-uix-label" }, label),
998
+ h(
999
+ "span",
1000
+ { className: "dsh-uix-input-wrap" },
1001
+ h("input", {
1002
+ className: "dsh-uix-input",
1003
+ type: "text",
1004
+ value,
1005
+ placeholder,
1006
+ spellCheck: false,
1007
+ autoComplete: "off",
1008
+ onChange: (event) => {
1009
+ set(event.target.value);
1010
+ }
1011
+ })
1012
+ ),
1013
+ hint ?? null
1014
+ );
1015
+ const editor = open ? h(
1016
+ "div",
1017
+ { className: "dsh-uix-form" },
1018
+ field(tr("palette.colors-path"), colors, DEFAULT_COLORS_PATH, setColors),
1019
+ field(
1020
+ tr("palette.tokens-path"),
1021
+ tokens,
1022
+ DEFAULT_TOKENS_PATH,
1023
+ setTokens,
1024
+ h("p", { className: "dsh-uix-hint" }, tr("palette.paths-hint"))
1025
+ ),
1026
+ h(
1027
+ "div",
1028
+ { className: "dsh-uix-footer" },
1029
+ h("button", {
1030
+ type: "button",
1031
+ className: "dsh-uix-save",
1032
+ disabled: state.saving,
1033
+ onClick: () => {
1034
+ void savePaths(colors.trim(), tokens.trim());
1035
+ }
1036
+ }, tr("palette.save")),
1037
+ h("button", {
1038
+ type: "button",
1039
+ className: "dsh-uix-reset",
1040
+ disabled: state.saving,
1041
+ onClick: () => {
1042
+ void savePaths(DEFAULT_COLORS_PATH, DEFAULT_TOKENS_PATH);
1043
+ }
1044
+ }, tr("palette.reset"))
1045
+ )
1046
+ ) : null;
1047
+ return h(
1048
+ "div",
1049
+ { className: "dsh-uix-row" },
1050
+ h(
1051
+ "div",
1052
+ { className: "dsh-uix-head" },
1053
+ h(
1054
+ "div",
1055
+ { className: "dsh-uix-text" },
1056
+ h("div", { className: "dsh-uix-title" }, tr("palette.title")),
1057
+ h("div", { className: "dsh-uix-desc" }, tr("palette.description"))
1058
+ ),
1059
+ h("button", {
1060
+ type: "button",
1061
+ role: "switch",
1062
+ "aria-checked": state.active,
1063
+ "aria-label": tr("palette.title"),
1064
+ disabled: state.saving,
1065
+ className: "dsh-uix-switch",
1066
+ onClick: () => {
1067
+ void setActive(!state.active);
1068
+ }
1069
+ }, h("span", { className: "dsh-uix-switch-thumb", "aria-hidden": true }))
1070
+ ),
1071
+ status,
1072
+ disclosure(open, () => {
1073
+ setOpen(!open);
1074
+ }, tr("palette.paths")),
1075
+ editor
1076
+ );
1077
+ }
1078
+ function MotionRow(props) {
1079
+ const tr = props.t ?? t;
1080
+ const [state, setState] = React.useState(view);
1081
+ const [open, setOpen] = React.useState(false);
1082
+ const [systemReduced, setSystemReduced] = React.useState(queryReduced());
1083
+ React.useEffect(() => {
1084
+ const listener = () => {
1085
+ setState(view);
1086
+ };
1087
+ listeners.add(listener);
1088
+ return () => {
1089
+ listeners.delete(listener);
1090
+ };
1091
+ }, []);
1092
+ React.useEffect(() => watchReduced(setSystemReduced), []);
1093
+ const current = state.motion;
1094
+ const locked = state.saving;
1095
+ const status = state.problem !== "" ? h("p", { className: "dsh-uix-problem", role: "alert" }, state.problem) : state.notice !== "" ? h("p", { className: "dsh-uix-notice" }, state.notice) : null;
1096
+ const modes = [
1097
+ { value: "off", label: tr("motion.mode-off") },
1098
+ { value: "m3", label: tr("motion.mode-m3") },
1099
+ { value: "m3e", label: tr("motion.mode-m3e") }
1100
+ ];
1101
+ const scales = [
1102
+ { value: "1", label: "1\xD7" },
1103
+ { value: "1.25", label: "1.25\xD7" },
1104
+ { value: "1.5", label: "1.5\xD7" },
1105
+ { value: "2", label: "2\xD7" }
1106
+ ];
1107
+ const policy = open ? h(
1108
+ "div",
1109
+ { className: "dsh-uix-form" },
1110
+ checkbox(
1111
+ "dsh-uix-motion-split",
1112
+ current.spatialSplit,
1113
+ (next) => {
1114
+ void saveMotion({ spatialSplit: next });
1115
+ },
1116
+ tr("motion.spatial-split"),
1117
+ tr("motion.spatial-split-hint"),
1118
+ locked
1119
+ ),
1120
+ checkbox(
1121
+ "dsh-uix-motion-quantize",
1122
+ current.quantize,
1123
+ (next) => {
1124
+ void saveMotion({ quantize: next });
1125
+ },
1126
+ tr("motion.quantize"),
1127
+ tr("motion.quantize-hint"),
1128
+ locked
1129
+ ),
1130
+ checkbox(
1131
+ "dsh-uix-motion-reduced",
1132
+ current.reduced === "always",
1133
+ (next) => {
1134
+ void saveMotion({ reduced: next ? "always" : "system" });
1135
+ },
1136
+ tr("motion.reduced"),
1137
+ tr("motion.reduced-hint"),
1138
+ locked
1139
+ )
1140
+ ) : null;
1141
+ return h(
1142
+ "div",
1143
+ { className: "dsh-uix-row" },
1144
+ h(
1145
+ "div",
1146
+ { className: "dsh-uix-head" },
1147
+ h(
1148
+ "div",
1149
+ { className: "dsh-uix-text" },
1150
+ h("div", { className: "dsh-uix-title" }, tr("motion.title")),
1151
+ h("div", { className: "dsh-uix-desc" }, tr("motion.description"))
1152
+ )
1153
+ ),
1154
+ status,
1155
+ h(
1156
+ "div",
1157
+ { className: "dsh-uix-choice" },
1158
+ h("span", { className: "dsh-uix-choice-label" }, tr("motion.mode")),
1159
+ segmented(
1160
+ "dsh-uix-motion-mode",
1161
+ current.mode,
1162
+ modes,
1163
+ (next) => {
1164
+ void saveMotion({ mode: next });
1165
+ },
1166
+ tr("motion.mode"),
1167
+ locked
1168
+ )
1169
+ ),
1170
+ current.mode === "off" ? null : h(
1171
+ "div",
1172
+ { className: "dsh-uix-choice" },
1173
+ h("span", { className: "dsh-uix-choice-label" }, tr("motion.duration")),
1174
+ segmented(
1175
+ "dsh-uix-motion-scale",
1176
+ String(current.durationScale),
1177
+ scales,
1178
+ (next) => {
1179
+ void saveMotion({ durationScale: Number(next) });
1180
+ },
1181
+ tr("motion.duration"),
1182
+ locked
1183
+ )
1184
+ ),
1185
+ current.mode !== "off" && systemReduced && current.reduced === "system" ? h("p", { className: "dsh-uix-hint" }, tr("motion.system-reduced")) : null,
1186
+ current.mode === "off" ? h("p", { className: "dsh-uix-hint" }, tr("motion.inert")) : null,
1187
+ disclosure(open, () => {
1188
+ setOpen(!open);
1189
+ }, tr("motion.options")),
1190
+ policy
1191
+ );
1192
+ }
1193
+ ctx.effect(() => ctx.slots.inject("settings.general.item", () => ctx.slots.register({
1194
+ name: "settings.general.item",
1195
+ id: "ui-experience.palette",
1196
+ order: 10.5,
1197
+ locale: "ui-experience"
1198
+ }, PaletteRow)), "ui-experience: settings row");
1199
+ ctx.effect(() => ctx.slots.inject("settings.general.item", () => ctx.slots.register({
1200
+ name: "settings.general.item",
1201
+ id: "ui-experience.motion",
1202
+ order: 10.6,
1203
+ locale: "ui-experience"
1204
+ }, MotionRow)), "ui-experience: motion row");
1205
+ ctx.effect(() => ctx.timer.interval(() => {
1206
+ void poll();
1207
+ }, POLL_MS), "ui-experience: palette polling");
1208
+ ctx.effect(() => {
1209
+ return () => {
1210
+ disposed = true;
1211
+ teardown();
1212
+ motion.dispose();
1213
+ };
1214
+ }, "ui-experience: page-level layers");
1215
+ void poll();
1216
+ }
1217
+ return {
1218
+ inject: ["slots", "locale", "timer", "theme", "remote"],
1219
+ async apply(ctx) {
1220
+ const theme = ctx.get("theme");
1221
+ if (theme === void 0) {
1222
+ console.error("ui-experience: ctx.theme is unavailable; the bundle cannot apply palettes");
1223
+ return;
1224
+ }
1225
+ const unmount = await ctx.remote.$mount(uiExperienceRemoteContribution(zod));
1226
+ ctx.effect(() => () => {
1227
+ unmount().catch((error) => {
1228
+ console.error(`ui-experience: remote unmount failed: ${error.message}`);
1229
+ });
1230
+ }, "ui-experience: remote namespace");
1231
+ const gateway = ctx.get(`remote.${REMOTE_SERVICE}`);
1232
+ if (gateway === void 0) {
1233
+ console.error("ui-experience: the remote namespace was not installed after mounting the contribution");
1234
+ return;
1235
+ }
1236
+ run(theme, ctx, gateway);
1237
+ }
1238
+ };
1239
+ }
1240
+ });
1241
+ }
1242
+ registerClient();
1243
+ })();