@weasel-js/forge 1.13.0 → 1.14.1

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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/{FrameController-DgfVaENb.d.ts → FrameController-BM6xuEfY.d.ts} +6 -10
  3. package/dist/{chunk-7XJNVJB5.js → chunk-B26DWEYT.js} +3 -3
  4. package/dist/{chunk-7XJNVJB5.js.map → chunk-B26DWEYT.js.map} +1 -1
  5. package/dist/{chunk-3BAM6M26.js → chunk-FPF2JSWB.js} +3 -3
  6. package/dist/chunk-FPF2JSWB.js.map +1 -0
  7. package/dist/{chunk-ORMMZ5PV.js → chunk-MRYXJTDO.js} +7 -3
  8. package/dist/chunk-MRYXJTDO.js.map +1 -0
  9. package/dist/{chunk-GHPMNT2A.js → chunk-PGLOYXDP.js} +184 -184
  10. package/dist/chunk-PGLOYXDP.js.map +1 -0
  11. package/dist/{chunk-WFT5IOHX.js → chunk-PPB7VP6R.js} +5 -4
  12. package/dist/chunk-PPB7VP6R.js.map +1 -0
  13. package/dist/{chunk-COT4I347.js → chunk-SFKDHXVE.js} +26 -4
  14. package/dist/chunk-SFKDHXVE.js.map +1 -0
  15. package/dist/chunk-STK2AZH2.js +11 -0
  16. package/dist/chunk-STK2AZH2.js.map +1 -0
  17. package/dist/chunk-TT4ZOYB6.js +143 -0
  18. package/dist/chunk-TT4ZOYB6.js.map +1 -0
  19. package/dist/{chunk-XIBKYSRU.js → chunk-UDEUT7AG.js} +124 -80
  20. package/dist/chunk-UDEUT7AG.js.map +1 -0
  21. package/dist/{config-6fWf87Tt.d.ts → config-DOjWyIui.d.ts} +1 -1
  22. package/dist/csf/preview-api.js +2 -2
  23. package/dist/frame/index.css +0 -2
  24. package/dist/frame/index.css.map +1 -1
  25. package/dist/frame/index.d.ts +4 -4
  26. package/dist/frame/index.js +9 -6
  27. package/dist/frame/index.js.map +1 -1
  28. package/dist/index.d.ts +11 -5
  29. package/dist/index.js +4 -4
  30. package/dist/{mountFrame-Bs7u1ro-.d.ts → mountFrame-CRPAjg1M.d.ts} +1 -1
  31. package/dist/shell/index.css +164 -147
  32. package/dist/shell/index.css.map +1 -1
  33. package/dist/shell/index.d.ts +23 -5
  34. package/dist/shell/index.js +454 -318
  35. package/dist/shell/index.js.map +1 -1
  36. package/dist/test/index.d.ts +1 -1
  37. package/dist/test/index.js +8 -10
  38. package/dist/test/index.js.map +1 -1
  39. package/dist/vite/index.js +118 -30
  40. package/dist/vite/index.js.map +1 -1
  41. package/package.json +11 -9
  42. package/dist/chunk-2ZBZRBHI.js +0 -10
  43. package/dist/chunk-2ZBZRBHI.js.map +0 -1
  44. package/dist/chunk-3BAM6M26.js.map +0 -1
  45. package/dist/chunk-COT4I347.js.map +0 -1
  46. package/dist/chunk-GHPMNT2A.js.map +0 -1
  47. package/dist/chunk-L5XM5VKB.js +0 -47
  48. package/dist/chunk-L5XM5VKB.js.map +0 -1
  49. package/dist/chunk-ORMMZ5PV.js.map +0 -1
  50. package/dist/chunk-WFT5IOHX.js.map +0 -1
  51. package/dist/chunk-XIBKYSRU.js.map +0 -1
package/README.md CHANGED
@@ -184,7 +184,7 @@ acts on the selected lens, or on the last one added when none is selected:
184
184
  | `o` | compare with the opposite mode | `k` | choose a comparison |
185
185
  | `Shift+O` | clear the comparison | `s` / `\` / `d` | Swap / Split / Difference |
186
186
  | `t` | Token readout | `c` | Contrast readout |
187
- | `g` | pixel grid | | |
187
+ | `g` | pixel grid | `b` | Glass: the reticle sits under the lens body |
188
188
 
189
189
  Each binding's id is its action id (`loupe.add`, `loupe.view.split`, …), so a
190
190
  binding override can rebind it. Alt+Slash opens a panel listing every key
@@ -74,6 +74,11 @@ interface A11yNode {
74
74
  target: readonly string[];
75
75
  html: string;
76
76
  failureSummary?: string;
77
+ /** On a node axe could not decide: why, as axe's message key (`bgOverlap`, `pseudoContent`, …) and its own sentence. */
78
+ reason?: {
79
+ key?: string;
80
+ message?: string;
81
+ };
77
82
  }
78
83
  /** One axe rule's outcome on the story, with the elements it names. */
79
84
  interface A11yFinding {
@@ -199,15 +204,6 @@ interface Span {
199
204
  start: number;
200
205
  end: number;
201
206
  }
202
- /** Everything a clock holds. */
203
- interface ClockState {
204
- /** The playhead, in ms, always inside `span`. */
205
- time: number;
206
- playing: boolean;
207
- loop: boolean;
208
- rate: number;
209
- span: Span;
210
- }
211
207
  /** A span replacing the declared one at runtime, as `StoryTimeline.setSpan` takes it. */
212
208
  type SpanOverride = Pick<TimelineSpec, 'duration' | 'start'>;
213
209
 
@@ -408,4 +404,4 @@ declare function startFrame({ story, channel, container, setup }: StartFrameOpti
408
404
  /** Reports an import failure — there is no story to start. */
409
405
  declare function reportImportFault(channel: Channel<ToFrame, FromFrame>, error: unknown): void;
410
406
 
411
- export { type A11yFinding as A, type SectionDescription as B, type Channel as C, type DepGraph as D, type FrameSetup as F, type Globals as G, type IndexEntry as I, type Layout as L, type MetaSpec as M, type PlayContext as P, type StartFrameOptions as S, type ToFrame as T, type Viewport as V, type ConfigAnswers as a, type FaultPhase as b, type FromFrame as c, type StorySpec as d, type ClockState as e, type SpanOverride as f, type A11yNode as g, type A11yReport as h, type ComponentDeps as i, type ComponentHistory as j, type Decorator as k, type GlobalsTarget as l, type IndexContext as m, type IndexRender as n, type IndexStoryProps as o, type LoadedStory as p, type StoryContext as q, reportImportFault as r, startFrame as s, type Span as t, type TimelineSpec as u, type CssVarReport as v, type A11yOutcome as w, type DescribedEntry as x, type DescribedShape as y, type SchemaDescription as z };
407
+ export { type A11yFinding as A, type Channel as C, type DepGraph as D, type FrameSetup as F, type Globals as G, type IndexEntry as I, type Layout as L, type MetaSpec as M, type PlayContext as P, type StartFrameOptions as S, type ToFrame as T, type Viewport as V, type ConfigAnswers as a, type FaultPhase as b, type FromFrame as c, type StorySpec as d, type Span as e, type SpanOverride as f, type A11yNode as g, type A11yReport as h, type ComponentDeps as i, type ComponentHistory as j, type Decorator as k, type GlobalsTarget as l, type IndexContext as m, type IndexRender as n, type IndexStoryProps as o, type LoadedStory as p, type StoryContext as q, reportImportFault as r, startFrame as s, type TimelineSpec as t, type CssVarReport as u, type A11yOutcome as v, type DescribedEntry as w, type DescribedShape as x, type SchemaDescription as y, type SectionDescription as z };
@@ -1,4 +1,4 @@
1
- import { stableStringify } from './chunk-2ZBZRBHI.js';
1
+ import { stableStringify } from './chunk-STK2AZH2.js';
2
2
  import { createContext } from 'react';
3
3
  import { isPlainObject } from '@weasel-js/core';
4
4
 
@@ -93,5 +93,5 @@ function withUnsent(sent, original) {
93
93
  }
94
94
 
95
95
  export { ArgsContext, appliedLocal, coverOf, isPortSafe, portSafePart, withUnsent };
96
- //# sourceMappingURL=chunk-7XJNVJB5.js.map
97
- //# sourceMappingURL=chunk-7XJNVJB5.js.map
96
+ //# sourceMappingURL=chunk-B26DWEYT.js.map
97
+ //# sourceMappingURL=chunk-B26DWEYT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/csf/argsContext.ts","../src/csf/portSafe.ts"],"names":[],"mappings":"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","file":"chunk-7XJNVJB5.js","sourcesContent":["import { type Context, createContext, type Dispatch, type SetStateAction } from 'react';\nimport { stableStringify } from '../protocol/messages';\n\ntype Args = Record<string, unknown>;\n\n/** A frame-local arg, and the config value it was set over: it applies only while config still holds that. */\nexport interface LocalArg {\n value: unknown;\n over: string;\n}\n\nexport type LocalArgs = Record<string, LocalArg>;\n\n/** A config value in the form `LocalArg.over` records. */\nexport const coverOf = (value: unknown): string => stableStringify(value) ?? ' undefined';\n\n/** The frame-local values that still apply over `config`. */\nexport const appliedLocal = (local: LocalArgs, config: Args): Args =>\n Object.fromEntries(\n Object.entries(local)\n .filter(([key, entry]) => coverOf(config[key]) === entry.over)\n .map(([key, entry]) => [key, entry.value]),\n );\n\nexport interface ArgsScope {\n /** The args the story renders with: its own, then config, then the frame-local ones that still apply. */\n args: Args;\n /** The trial's config as it stands. An `undefined` key means the story's own arg. */\n config: Args;\n defaults: Args;\n /** The story's own args, before config and the frame-local ones. */\n original: Args;\n setConfig: (path: string, value: unknown) => void;\n /** Args a MessagePort would not deliver intact, kept in the frame because config crosses one. */\n setLocal: Dispatch<SetStateAction<LocalArgs>>;\n}\n\nexport const ArgsContext: Context<ArgsScope | null> = createContext<ArgsScope | null>(null);\n","import { isPlainObject } from '@weasel-js/core';\n\nconst g = globalThis as Record<string, unknown>;\nconst CLONED_WHOLE = new Set<unknown>(\n [\n 'Date',\n 'RegExp',\n 'ArrayBuffer',\n 'DataView',\n 'Int8Array',\n 'Uint8Array',\n 'Uint8ClampedArray',\n 'Int16Array',\n 'Uint16Array',\n 'Int32Array',\n 'Uint32Array',\n 'Float16Array',\n 'Float32Array',\n 'Float64Array',\n 'BigInt64Array',\n 'BigUint64Array',\n ]\n .map((name) => (g[name] as { prototype?: object } | undefined)?.prototype)\n .filter(Boolean),\n);\n\n/** Own keys a clone keeps: enumerable string keys, plus an array's `length`. */\nconst hasOnlyClonedKeys = (v: object): boolean =>\n Reflect.ownKeys(v).every(\n (key) =>\n typeof key === 'string' &&\n (Object.prototype.propertyIsEnumerable.call(v, key) || (Array.isArray(v) && key === 'length')),\n );\n\n/**\n * Whether a MessagePort delivers `value` as the same thing it was. `structuredClone` succeeding is\n * not enough: it copies a class instance's fields onto a plain object and drops its methods.\n */\nexport function isPortSafe(value: unknown): boolean {\n const seen = new Set<object>();\n const safe = (v: unknown): boolean => {\n if (v === null) return true;\n if (typeof v !== 'object') return typeof v !== 'function' && typeof v !== 'symbol';\n if (seen.has(v)) return true;\n seen.add(v);\n const proto = Object.getPrototypeOf(v);\n if (CLONED_WHOLE.has(proto)) return true;\n if (proto === Map.prototype)\n return hasOnlyClonedKeys(v) && [...(v as Map<unknown, unknown>)].every(([key, member]) => safe(key) && safe(member));\n if (proto === Set.prototype) return hasOnlyClonedKeys(v) && [...(v as Set<unknown>)].every(safe);\n if (proto !== Array.prototype && proto !== Object.prototype && proto !== null) return false;\n return hasOnlyClonedKeys(v) && Object.values(v).every(safe);\n };\n try {\n return safe(value);\n } catch {\n return false;\n }\n}\n\n/** A plain object or array whose members can be split: React elements are plain objects, but whole. */\nconst splittable = (v: unknown): v is Record<string, unknown> | unknown[] =>\n (Array.isArray(v) ? Object.getPrototypeOf(v) === Array.prototype : isPlainObject(v) && !('$$typeof' in v)) &&\n hasOnlyClonedKeys(v as object);\n\n/**\n * The part of `value` a port delivers intact, or null when none of it can cross. A plain object\n * keeps its port-safe fields; an array holds null where a member has no part.\n */\nexport function portSafePart(value: unknown): { value: unknown } | null {\n const walking = new Set<object>();\n const part = (v: unknown): { value: unknown } | null => {\n if (isPortSafe(v)) return { value: v };\n if (!splittable(v) || walking.has(v)) return null;\n walking.add(v);\n try {\n if (Array.isArray(v)) return { value: v.map((member) => part(member)?.value ?? null) };\n const out: Record<string, unknown> = {};\n for (const [key, member] of Object.entries(v)) {\n const kept = part(member);\n if (kept) out[key] = kept.value;\n }\n return { value: out };\n } finally {\n walking.delete(v);\n }\n };\n try {\n return part(value);\n } catch {\n return null;\n }\n}\n\n/** `sent`, a value that crossed the port as `portSafePart(original)` or an edit of it, with what the part left behind put back. */\nexport function withUnsent(sent: unknown, original: unknown): unknown {\n if (isPortSafe(original)) return sent;\n if (sent === null && portSafePart(original) === null) return original;\n if (Array.isArray(original) && Array.isArray(sent))\n return sent.map((member, i) => (i < original.length ? withUnsent(member, original[i]) : member));\n if (!splittable(original) || Array.isArray(original) || !isPlainObject(sent)) return sent;\n const out: Record<string, unknown> = {};\n for (const [key, member] of Object.entries(original)) {\n if (key in sent) out[key] = withUnsent(sent[key], member);\n else if (portSafePart(member) === null) out[key] = member;\n }\n for (const [key, member] of Object.entries(sent)) if (!(key in original)) out[key] = member;\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/csf/argsContext.ts","../src/csf/portSafe.ts"],"names":[],"mappings":"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","file":"chunk-B26DWEYT.js","sourcesContent":["import { type Context, createContext, type Dispatch, type SetStateAction } from 'react';\nimport { stableStringify } from '../protocol/messages';\n\ntype Args = Record<string, unknown>;\n\n/** A frame-local arg, and the config value it was set over: it applies only while config still holds that. */\nexport interface LocalArg {\n value: unknown;\n over: string;\n}\n\nexport type LocalArgs = Record<string, LocalArg>;\n\n/** A config value in the form `LocalArg.over` records. */\nexport const coverOf = (value: unknown): string => stableStringify(value) ?? ' undefined';\n\n/** The frame-local values that still apply over `config`. */\nexport const appliedLocal = (local: LocalArgs, config: Args): Args =>\n Object.fromEntries(\n Object.entries(local)\n .filter(([key, entry]) => coverOf(config[key]) === entry.over)\n .map(([key, entry]) => [key, entry.value]),\n );\n\nexport interface ArgsScope {\n /** The args the story renders with: its own, then config, then the frame-local ones that still apply. */\n args: Args;\n /** The trial's config as it stands. An `undefined` key means the story's own arg. */\n config: Args;\n defaults: Args;\n /** The story's own args, before config and the frame-local ones. */\n original: Args;\n setConfig: (path: string, value: unknown) => void;\n /** Args a MessagePort would not deliver intact, kept in the frame because config crosses one. */\n setLocal: Dispatch<SetStateAction<LocalArgs>>;\n}\n\nexport const ArgsContext: Context<ArgsScope | null> = createContext<ArgsScope | null>(null);\n","import { isPlainObject } from '@weasel-js/core';\n\nconst g = globalThis as Record<string, unknown>;\nconst CLONED_WHOLE = new Set<unknown>(\n [\n 'Date',\n 'RegExp',\n 'ArrayBuffer',\n 'DataView',\n 'Int8Array',\n 'Uint8Array',\n 'Uint8ClampedArray',\n 'Int16Array',\n 'Uint16Array',\n 'Int32Array',\n 'Uint32Array',\n 'Float16Array',\n 'Float32Array',\n 'Float64Array',\n 'BigInt64Array',\n 'BigUint64Array',\n ]\n .map((name) => (g[name] as { prototype?: object } | undefined)?.prototype)\n .filter(Boolean),\n);\n\n/** Own keys a clone keeps: enumerable string keys, plus an array's `length`. */\nconst hasOnlyClonedKeys = (v: object): boolean =>\n Reflect.ownKeys(v).every(\n (key) =>\n typeof key === 'string' &&\n (Object.prototype.propertyIsEnumerable.call(v, key) || (Array.isArray(v) && key === 'length')),\n );\n\n/**\n * Whether a MessagePort delivers `value` as the same thing it was. `structuredClone` succeeding is\n * not enough: it copies a class instance's fields onto a plain object and drops its methods.\n */\nexport function isPortSafe(value: unknown): boolean {\n const seen = new Set<object>();\n const safe = (v: unknown): boolean => {\n if (v === null) return true;\n if (typeof v !== 'object') return typeof v !== 'function' && typeof v !== 'symbol';\n if (seen.has(v)) return true;\n seen.add(v);\n const proto = Object.getPrototypeOf(v);\n if (CLONED_WHOLE.has(proto)) return true;\n if (proto === Map.prototype)\n return hasOnlyClonedKeys(v) && [...(v as Map<unknown, unknown>)].every(([key, member]) => safe(key) && safe(member));\n if (proto === Set.prototype) return hasOnlyClonedKeys(v) && [...(v as Set<unknown>)].every(safe);\n if (proto !== Array.prototype && proto !== Object.prototype && proto !== null) return false;\n return hasOnlyClonedKeys(v) && Object.values(v).every(safe);\n };\n try {\n return safe(value);\n } catch {\n return false;\n }\n}\n\n/** A plain object or array whose members can be split: React elements are plain objects, but whole. */\nconst splittable = (v: unknown): v is Record<string, unknown> | unknown[] =>\n (Array.isArray(v) ? Object.getPrototypeOf(v) === Array.prototype : isPlainObject(v) && !('$$typeof' in v)) &&\n hasOnlyClonedKeys(v as object);\n\n/**\n * The part of `value` a port delivers intact, or null when none of it can cross. A plain object\n * keeps its port-safe fields; an array holds null where a member has no part.\n */\nexport function portSafePart(value: unknown): { value: unknown } | null {\n const walking = new Set<object>();\n const part = (v: unknown): { value: unknown } | null => {\n if (isPortSafe(v)) return { value: v };\n if (!splittable(v) || walking.has(v)) return null;\n walking.add(v);\n try {\n if (Array.isArray(v)) return { value: v.map((member) => part(member)?.value ?? null) };\n const out: Record<string, unknown> = {};\n for (const [key, member] of Object.entries(v)) {\n const kept = part(member);\n if (kept) out[key] = kept.value;\n }\n return { value: out };\n } finally {\n walking.delete(v);\n }\n };\n try {\n return part(value);\n } catch {\n return null;\n }\n}\n\n/** `sent`, a value that crossed the port as `portSafePart(original)` or an edit of it, with what the part left behind put back. */\nexport function withUnsent(sent: unknown, original: unknown): unknown {\n if (isPortSafe(original)) return sent;\n if (sent === null && portSafePart(original) === null) return original;\n if (Array.isArray(original) && Array.isArray(sent))\n return sent.map((member, i) => (i < original.length ? withUnsent(member, original[i]) : member));\n if (!splittable(original) || Array.isArray(original) || !isPlainObject(sent)) return sent;\n const out: Record<string, unknown> = {};\n for (const [key, member] of Object.entries(original)) {\n if (key in sent) out[key] = withUnsent(sent[key], member);\n else if (portSafePart(member) === null) out[key] = member;\n }\n for (const [key, member] of Object.entries(sent)) if (!(key in original)) out[key] = member;\n return out;\n}\n"]}
@@ -76,6 +76,6 @@ function withGlobals(schema, declarations) {
76
76
  return f.schema({ ...schema.nodes, [GLOBALS_KEY]: group });
77
77
  }
78
78
 
79
- export { FOLLOW_APP, GLOBALS_KEY, PLAYHEAD_KEY, effectiveGlobals, followApp, isForgePath, isGlobalsPath, labGlobals, labOptions, storyConfig, withGlobals };
80
- //# sourceMappingURL=chunk-3BAM6M26.js.map
81
- //# sourceMappingURL=chunk-3BAM6M26.js.map
79
+ export { FOLLOW_APP, GLOBALS_KEY, effectiveGlobals, followApp, isForgePath, isGlobalsPath, labGlobals, labOptions, storyConfig, withGlobals };
80
+ //# sourceMappingURL=chunk-FPF2JSWB.js.map
81
+ //# sourceMappingURL=chunk-FPF2JSWB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shell/globals.ts"],"names":["from"],"mappings":";;;;AA2CO,IAAM,WAAA,GAAc;AAEpB,IAAM,UAAA,GAAa,KAAA;AAEnB,IAAM,UAAA,GAAa;AAE1B,IAAM,UAAA,GAAa,EAAE,KAAA,EAAO,UAAA,EAAY,OAAO,KAAA,EAAM;AAErD,IAAM,QAAA,GAAW,aAAA;AAEV,IAAM,aAAA,GAAgB,CAAC,IAAA,KAA0B,IAAA,KAAS,eAAe,IAAA,CAAK,UAAA,CAAW,CAAA,EAAG,WAAW,CAAA,CAAA,CAAG;AAG1G,IAAM,YAAA,GAAe,WAAA;AAG5B,IAAM,6BAAkC,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,YAAY,CAAC,CAAA;AAGpE,IAAM,WAAA,GAAc,CAAC,IAAA,KAA0B,UAAA,CAAW,GAAA,CAAI,KAAK,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAE;AAGjF,SAAS,YAAA,CAAa,aAAgC,OAAA,EAAgD;AAC3G,EAAA,MAAM,EAAE,OAAM,GAAI,WAAA;AAClB,EAAA,OAAO,KAAA,GAAQ,WAAA,CAAY,OAAA,CAAQ,MAAA,CAAO,CAAC,MAAA,KAAW,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,OAAO,CAAC,CAAA,GAAI,WAAA,CAAY,OAAA;AACpG;AAGO,SAAS,UAAA,CAAW,aAAgC,OAAA,EAAgD;AACzG,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,WAAA,EAAa,OAAO,CAAA;AAC/C,EAAA,OAAO,YAAY,OAAA,GAAU,CAAC,UAAA,EAAY,GAAG,KAAK,CAAA,GAAI,KAAA;AACxD;AAGO,SAAS,SAAA,CAAU,YAAA,EAAkC,MAAA,EAAiB,MAAA,EAA4B;AACvG,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAA,CAAE,OAAA;AAAA,IAAQ,CAAC,CAAC,GAAA,EAAK,EAAE,SAAS,CAAA,KACtE,WAAW,MAAA,CAAO,GAAG,MAAM,UAAA,GAAa,CAAC,CAAC,GAAA,EAAK,OAAA,CAAQ,MAAM,CAAC,CAAU,IAAI;AAAC,GAC/E;AACA,EAAA,OAAO,QAAA,CAAS,MAAA,KAAW,CAAA,GAAI,MAAA,GAAS,EAAE,GAAG,MAAA,EAAQ,GAAG,MAAA,CAAO,WAAA,CAAY,QAAQ,CAAA,EAAE;AACvF;AAMO,SAAS,UAAA,CAAW,cAAkC,MAAA,EAA0B;AACrF,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,MAAM,CAAA,GAAI,SAAS,EAAC;AAC1C,EAAA,MAAM,UAAmB,MAAA,CAAO,WAAA;AAAA,IAC9B,MAAA,CAAO,QAAQ,YAAY,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,WAAW,CAAA,KAAM;AACvD,MAAA,MAAM,KAAA,GAAQ,KAAK,GAAG,CAAA;AACtB,MAAA,MAAM,KAAA,GACJ,WAAA,CAAY,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,KAAA,KAAU,KAAK,CAAA,IAAM,WAAA,CAAY,OAAA,IAAW,KAAA,KAAU,UAAA;AACpG,MAAA,OAAO,CAAC,GAAA,EAAK,KAAA,GAAQ,KAAA,GAAQ,YAAY,OAAO,CAAA;AAAA,IAClD,CAAC;AAAA,GACH;AACA,EAAA,OAAO,MAAA,CAAO,WAAA;AAAA,IACZ,MAAA,CAAO,QAAQ,YAAY,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,WAAW,CAAA,KAAM;AACvD,MAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,WAAA,EAAa,OAAO,CAAA;AAC/C,MAAA,MAAM,KAAA,GAAQ,QAAQ,GAAG,CAAA;AACzB,MAAA,IAAI,UAAU,UAAA,IAAc,WAAA,CAAY,SAAS,OAAO,CAAC,KAAK,KAAK,CAAA;AACnE,MAAA,IAAI,CAAC,WAAA,CAAY,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,KAAK,KAAA,CAAM,IAAA,CAAK,CAAC,MAAA,KAAW,OAAO,KAAA,KAAU,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,CAAA;AAClH,MAAA,MAAM,EAAA,GAAK,CAAC,CAAA,KAAe,WAAA,CAAY,OAAA,CAAQ,UAAU,CAAC,MAAA,KAAW,MAAA,CAAO,KAAA,KAAU,CAAC,CAAA;AACvF,MAAA,MAAMA,KAAAA,GAAO,GAAG,KAAK,CAAA;AACrB,MAAA,MAAM,UAAU,KAAA,CAAM,MAAA;AAAA,QAAO,CAAC,IAAA,EAAM,MAAA,KAClC,KAAK,GAAA,CAAI,EAAA,CAAG,OAAO,KAAK,CAAA,GAAIA,KAAI,CAAA,GAAI,IAAA,CAAK,IAAI,EAAA,CAAG,IAAA,CAAK,KAAK,CAAA,GAAIA,KAAI,IAAI,MAAA,GAAS;AAAA,OACjF;AACA,MAAA,OAAO,CAAC,GAAA,EAAK,OAAA,CAAQ,KAAK,CAAA;AAAA,IAC5B,CAAC;AAAA,GACH;AACF;AAGO,SAAS,gBAAA,CAAiB,KAAc,IAAA,EAAwB;AACrE,EAAA,IAAI,CAAC,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,GAAA;AAC5B,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,IAAI,EAAE,MAAA,CAAO,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,GAAA,IAAO,GAAA,IAAO,UAAU,UAAU,CAAA;AAC/F,EAAA,OAAO,MAAA,CAAO,MAAA,KAAW,CAAA,GAAI,GAAA,GAAM,EAAE,GAAG,GAAA,EAAK,GAAG,MAAA,CAAO,WAAA,CAAY,MAAM,CAAA,EAAE;AAC7E;AAMO,SAAS,WAAA,CAAY,QAAiB,QAAA,EAA6B;AACxE,EAAA,IAAI,CAAC,QAAA,CAAS,MAAM,CAAA,IAAK,CAAC,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,GAAA,KAAQ,UAAA,CAAW,IAAI,GAAG,CAAC,GAAG,OAAO,MAAA;AACzF,EAAA,MAAM,OAAO,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,CAAC,WAAW,GAAA,CAAI,GAAG,CAAC,CAAC,CAAA;AAC9F,EAAA,IAAI,QAAA,CAAS,QAAQ,CAAA,EAAG;AACtB,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAC7B,IAAA,IAAI,KAAK,MAAA,KAAW,MAAA,CAAO,KAAK,QAAQ,CAAA,CAAE,UAAU,IAAA,CAAK,KAAA,CAAM,CAAC,GAAA,KAAQ,KAAK,GAAG,CAAA,KAAM,SAAS,GAAG,CAAC,GAAG,OAAO,QAAA;AAAA,EAC/G;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,WAAA,CAAY,QAA+B,YAAA,EAAyD;AAClH,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAA;AAC3C,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AACjC,EAAA,MAAM,SAAS,MAAA,CAAO,WAAA;AAAA,IACpB,QAAQ,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,WAAW,CAAA,KAAM;AAAA,MAClC,GAAA;AAAA,MACA,EAAE,IAAA,CAAK,UAAA,EAAY,CAAC,EAAE,OAAO,UAAA,EAAY,KAAA,EAAO,KAAA,EAAM,EAAG,GAAG,WAAA,CAAY,OAAO,CAAC,CAAA,CAAE,KAAA,CAAM,YAAY,KAAK;AAAA,KAC1G;AAAA,GACH;AAEA,EAAA,MAAM,QAAQ,CAAA,CAAE,KAAA,CAAM,MAAM,CAAA,CAAE,MAAM,EAAE,CAAA,CAAE,OAAA,CAAQ,SAAA,EAAW,EAAE,MAAA,EAAQ,QAAA,EAAU,MAAM,OAAA,EAAS,SAAA,EAAW,mBAAmB,CAAA;AAC5H,EAAA,OAAO,CAAA,CAAE,MAAA,CAAO,EAAE,GAAG,MAAA,CAAO,OAAO,CAAC,WAAW,GAAG,KAAA,EAAO,CAAA;AAC3D","file":"chunk-FPF2JSWB.js","sourcesContent":["import { isPlainObject } from '@weasel-js/core';\nimport type { IconRef } from '@weasel-js/icons';\nimport type { LabMode } from '@weasel-js/labkit';\nimport { type ConfigSchema, f } from '@weasel-js/labkit/config';\nimport type { Globals } from '../protocol/messages';\n\n/** One global the workshop's toolbar sets for every story, and a trial may pin for its own. */\nexport interface GlobalDeclaration {\n label: string;\n options: readonly { value: string; label: string }[];\n default: string;\n /** Another global's key: the toolbar shows this one in a popover beside that one rather than in the bar itself. */\n under?: string;\n /** The glyph on the button opening the globals declared `under` this one. Default `core:tune`. */\n icon?: IconRef;\n /**\n * Whether the toolbar offers `value` given the lab's other values; every option shows when absent. `globals` is\n * resolved without any declaration's `shows`, so one `shows` cannot depend on another's outcome.\n */\n shows?: (value: string, globals: Globals) => boolean;\n /**\n * What this global reads from the lab's own chrome. Given, the lab's value may be `App` (`FOLLOW_APP`), offered\n * first, and while it is the global follows that chrome control, as the header's mode switch sets it. A trial's\n * pin follows the lab or names a value; it cannot follow the app on its own.\n */\n follows?: (chrome: LabChrome) => string;\n /**\n * The value opposite `value`, for the global a lens's opposite-mode comparison flips (`ShellConfig.loupe`'s\n * `modeGlobal`); undefined when it has none. `App` and `auto` are resolved to the mode they show before this is\n * asked. Absent, the lens swaps `light` and `dark`.\n */\n opposite?: (value: string, globals: Globals) => string | undefined;\n}\n\n/** The lab chrome's own settings a global can follow. */\nexport interface LabChrome {\n mode: LabMode;\n}\n\n/** The shell config's `globals`: each declaration by the key stories read it under. */\nexport type GlobalDeclarations = Readonly<Record<string, GlobalDeclaration>>;\n\n/** The config group every story schema gains, holding the trial's pins. A story never sees it. */\nexport const GLOBALS_KEY = '$globals';\n/** A pin's value when the trial follows the lab. */\nexport const FOLLOW_LAB = 'lab';\n/** A lab value, for a declaration that `follows` the app, meaning whatever the app's chrome is set to. */\nexport const FOLLOW_APP = 'app';\n\nconst APP_OPTION = { value: FOLLOW_APP, label: 'App' } as const;\n\nconst isRecord = isPlainObject;\n\nexport const isGlobalsPath = (path: string): boolean => path === GLOBALS_KEY || path.startsWith(`${GLOBALS_KEY}.`);\n\n/** The config key forge once kept a paused playhead under; a saved config may still hold it, so a story never sees it. */\nexport const PLAYHEAD_KEY = '$playhead';\n\n/** Config keys forge owns: a story neither sees nor writes them. */\nconst FORGE_KEYS: ReadonlySet<string> = new Set([GLOBALS_KEY, PLAYHEAD_KEY]);\n\n/** Whether a config path lies under a key forge owns. */\nexport const isForgePath = (path: string): boolean => FORGE_KEYS.has(path.split('.')[0]!);\n\n/** The options the toolbar offers for `declaration` at `globals`. */\nexport function shownOptions(declaration: GlobalDeclaration, globals: Globals): GlobalDeclaration['options'] {\n const { shows } = declaration;\n return shows ? declaration.options.filter((option) => shows(option.value, globals)) : declaration.options;\n}\n\n/** The options the lab's own control offers: `shownOptions`, after `App` where the declaration follows the app. */\nexport function labOptions(declaration: GlobalDeclaration, globals: Globals): GlobalDeclaration['options'] {\n const shown = shownOptions(declaration, globals);\n return declaration.follows ? [APP_OPTION, ...shown] : shown;\n}\n\n/** `values` with each `App` replaced by what its declaration reads from `chrome`; `values` itself when none is. */\nexport function followApp(declarations: GlobalDeclarations, values: Globals, chrome: LabChrome): Globals {\n const followed = Object.entries(declarations).flatMap(([key, { follows }]) =>\n follows && values[key] === FOLLOW_APP ? [[key, follows(chrome)] as const] : [],\n );\n return followed.length === 0 ? values : { ...values, ...Object.fromEntries(followed) };\n}\n\n/**\n * The lab's values from what was stored: declared keys only. A value no option offers falls back to the default, and\n * one its declaration's `shows` hides falls back to the shown option nearest it in `options`, the earlier on a tie.\n */\nexport function labGlobals(declarations: GlobalDeclarations, stored: unknown): Globals {\n const from = isRecord(stored) ? stored : {};\n const offered: Globals = Object.fromEntries(\n Object.entries(declarations).map(([key, declaration]) => {\n const value = from[key];\n const known =\n declaration.options.some((option) => option.value === value) || (declaration.follows && value === FOLLOW_APP);\n return [key, known ? value : declaration.default];\n }),\n );\n return Object.fromEntries(\n Object.entries(declarations).map(([key, declaration]) => {\n const shown = shownOptions(declaration, offered);\n const value = offered[key];\n if (value === FOLLOW_APP && declaration.follows) return [key, value];\n if (!declaration.shows || shown.length === 0 || shown.some((option) => option.value === value)) return [key, value];\n const at = (v: unknown) => declaration.options.findIndex((option) => option.value === v);\n const from = at(value);\n const nearest = shown.reduce((best, option) =>\n Math.abs(at(option.value) - from) < Math.abs(at(best.value) - from) ? option : best,\n );\n return [key, nearest.value];\n }),\n );\n}\n\n/** The lab's values, overridden by the pins in a trial's config that do not follow the lab. */\nexport function effectiveGlobals(lab: Globals, pins: unknown): Globals {\n if (!isRecord(pins)) return lab;\n const pinned = Object.entries(pins).filter(([key, value]) => key in lab && value !== FOLLOW_LAB);\n return pinned.length === 0 ? lab : { ...lab, ...Object.fromEntries(pinned) };\n}\n\n/**\n * The config a story receives: the trial's, without the keys forge owns. Returns `previous` when every other entry is\n * the same, so a change to a pin or the playhead alone is not a change to the story's config.\n */\nexport function storyConfig(config: unknown, previous?: unknown): unknown {\n if (!isRecord(config) || !Object.keys(config).some((key) => FORGE_KEYS.has(key))) return config;\n const rest = Object.fromEntries(Object.entries(config).filter(([key]) => !FORGE_KEYS.has(key)));\n if (isRecord(previous)) {\n const keys = Object.keys(rest);\n if (keys.length === Object.keys(previous).length && keys.every((key) => rest[key] === previous[key])) return previous;\n }\n return rest;\n}\n\n/** `schema` with a `$globals` group appended: one leaf per declaration, following the lab until pinned. */\nexport function withGlobals(schema: ConfigSchema<unknown>, declarations: GlobalDeclarations): ConfigSchema<unknown> {\n const entries = Object.entries(declarations);\n if (entries.length === 0) return schema;\n const leaves = Object.fromEntries(\n entries.map(([key, declaration]) => [\n key,\n f.enum(FOLLOW_LAB, [{ value: FOLLOW_LAB, label: 'Lab' }, ...declaration.options]).label(declaration.label),\n ]),\n );\n // An empty group name contributes rows without a heading of its own, under the section's.\n const group = f.group(leaves).label('').section('Globals', { layout: 'inline', pack: 'pairs', className: 'fg-globals-pins' });\n return f.schema({ ...schema.nodes, [GLOBALS_KEY]: group }) as ConfigSchema<unknown>;\n}\n"]}
@@ -3,6 +3,10 @@ import { isSafeOverride } from './chunk-5WS67HMU.js';
3
3
  // src/title.ts
4
4
  var DEFAULT_TITLE = "weaselforge";
5
5
 
6
+ // src/vite/issues/answer.ts
7
+ var ISSUES_PATH = "__forge/issues";
8
+ var issueLabel = (component) => `forge:${component}`;
9
+
6
10
  // src/vite/capture/payload.ts
7
11
  var messageOf = (err) => err instanceof Error ? err.message : String(err);
8
12
  var CAPTURE_PATH = "__forge/capture";
@@ -54,6 +58,6 @@ function parsePayload(body) {
54
58
  };
55
59
  }
56
60
 
57
- export { CAPTURE_PAGES, CAPTURE_PATH, DEFAULT_TITLE, MAX_DPR, MIN_DPR, messageOf, parsePayload };
58
- //# sourceMappingURL=chunk-ORMMZ5PV.js.map
59
- //# sourceMappingURL=chunk-ORMMZ5PV.js.map
61
+ export { CAPTURE_PAGES, CAPTURE_PATH, DEFAULT_TITLE, ISSUES_PATH, MAX_DPR, MIN_DPR, issueLabel, messageOf, parsePayload };
62
+ //# sourceMappingURL=chunk-MRYXJTDO.js.map
63
+ //# sourceMappingURL=chunk-MRYXJTDO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/title.ts","../src/vite/issues/answer.ts","../src/vite/capture/payload.ts"],"names":[],"mappings":"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","file":"chunk-MRYXJTDO.js","sourcesContent":["/** The workshop's name when the shell config gives none: in its header, and as the document title. */\nexport const DEFAULT_TITLE = 'weaselforge';\n","/** Where the issues endpoint answers, under the dev server's base. */\nexport const ISSUES_PATH = '__forge/issues';\n\n/** The label a component's issues carry. */\nexport const issueLabel = (component: string): string => `forge:${component}`;\n\n/** An open issue filed against a component. */\nexport interface ForgeIssue {\n number: number;\n title: string;\n url: string;\n /** When it was opened, as an ISO date. */\n opened: string;\n}\n\n/** The GitHub repository the dev server's checkout pushes to. */\nexport interface IssueRepo {\n /** `owner/name`. */\n slug: string;\n /** Its page, such as `https://github.com/owner/name`. */\n url: string;\n}\n\n/**\n * What a GET of the endpoint answers. `repo`, `branch` and `commit` describe the checkout whether or not `gh` can\n * list anything; exactly one of `issues`, `unavailable` and `error` is set.\n */\nexport interface IssuesAnswer {\n /** Null when `origin` names no GitHub repository. */\n repo: IssueRepo | null;\n /** Null on a detached head. */\n branch: string | null;\n commit: string | null;\n issues?: ForgeIssue[];\n /** Why nothing can be listed, when that is the machine's setup rather than a fault: no `gh`, no login, no repo. */\n unavailable?: string;\n /** What went wrong when `gh` was there and failed. */\n error?: string;\n}\n","import { isSafeOverride } from '../../frame/safeOverride.ts';\nimport type { Globals, Layout } from '../../protocol/messages.ts';\n\n/** Everything the screen renders a story from, as the capture endpoint takes it. */\nexport interface CapturePayload {\n /** The story's index id. */\n story: string;\n /** Picks the document: an isolated story renders under frame styles, any other under shell styles. */\n isolate: boolean;\n /** The story's config, with forge's `$` keys stripped and a comparison's args laid over. */\n config: unknown;\n /** Must survive JSON. */\n state: unknown;\n globals: Globals;\n /** CSS variable overrides, by property name. */\n overrides: Readonly<Record<string, string>>;\n /** Null for a story with no timeline, and for an isolated story. */\n playhead: number | null;\n /** The host's size in CSS pixels. */\n box: { width: number; height: number };\n layout: Layout;\n /** Device pixel ratio, in [1, 3]. */\n dpr: number;\n /** Where the story's scroller stands in its host, in CSS pixels, so the capture shows the patch the screen does. */\n scroll: { left: number; top: number };\n /** The workshop lab's theme as the screen shows it: the lab's globals, which the shell config's `labTheme` reads,\n * and its resolved color mode. An isolated story's document has no lab, and ignores it. */\n lab: { globals: Globals; mode: 'light' | 'dark' };\n /** The OS color scheme the screen sees, which the capture page emulates: a story or a lab in `auto` mode follows\n * `prefers-color-scheme`, and headless Chromium would otherwise answer light. */\n colorScheme: 'light' | 'dark';\n}\n\n/** What a capture failure says: an error's message, or anything else as a string. */\nexport const messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));\n\n/** Where the endpoint answers, under the dev server's base. */\nexport const CAPTURE_PATH = '__forge/capture';\n\n/** The pages a capture renders on, under the dev server's base: one per document a story can render in. */\nexport const CAPTURE_PAGES = { shell: '__forge/capture.html', frame: '__forge/capture-frame.html' };\n\nexport const MIN_DPR = 1;\nexport const MAX_DPR = 3;\n\nconst LAYOUTS: readonly string[] = ['centered', 'padded', 'fullscreen'] satisfies readonly Layout[];\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n);\nconst positive = (n: unknown): n is number => finite(n) && n > 0;\n\n/** Reads a request body as a payload, or names the first field it rejects. */\nexport function parsePayload(body: unknown): CapturePayload | { error: string } {\n if (!isRecord(body)) return { error: 'a capture payload is an object' };\n const { story, isolate, config, state, globals, overrides, playhead, box, layout, dpr, scroll, lab, colorScheme } = body;\n if (typeof story !== 'string' || story === '') return { error: 'story must be an index id' };\n if (typeof isolate !== 'boolean') return { error: 'isolate must be a boolean' };\n if (!isRecord(globals)) return { error: 'globals must be an object' };\n if (!isRecord(overrides) || !Object.values(overrides).every((v) => typeof v === 'string')) {\n return { error: 'overrides must map property names to strings' };\n }\n const unsafe = Object.entries(overrides as Record<string, string>).find(([name, value]) => !isSafeOverride(name, value));\n if (unsafe) return { error: `overrides must name custom properties, with no { } or ; in a value: ${unsafe[0]}` };\n if (playhead !== null && !(typeof playhead === 'number' && Number.isFinite(playhead))) {\n return { error: 'playhead must be a number or null' };\n }\n if (!isRecord(box) || !positive(box.width) || !positive(box.height)) {\n return { error: 'box must have a positive width and height' };\n }\n if (typeof layout !== 'string' || !LAYOUTS.includes(layout)) return { error: `layout must be one of ${LAYOUTS.join(', ')}` };\n if (typeof dpr !== 'number' || !(dpr >= MIN_DPR && dpr <= MAX_DPR)) return { error: `dpr must be in [${MIN_DPR}, ${MAX_DPR}]` };\n if (!isRecord(scroll) || !finite(scroll.left) || !finite(scroll.top)) return { error: 'scroll must hold a finite left and top' };\n if (!isRecord(lab) || !isRecord(lab.globals) || (lab.mode !== 'light' && lab.mode !== 'dark')) {\n return { error: 'lab must hold globals and a mode of light or dark' };\n }\n if (colorScheme !== 'light' && colorScheme !== 'dark') return { error: 'colorScheme must be light or dark' };\n return {\n story,\n isolate,\n config,\n state,\n globals,\n overrides: overrides as Record<string, string>,\n playhead,\n box: { width: box.width, height: box.height },\n layout: layout as Layout,\n dpr,\n scroll: { left: scroll.left, top: scroll.top },\n lab: { globals: lab.globals, mode: lab.mode },\n colorScheme,\n };\n}\n"]}
@@ -1,28 +1,145 @@
1
- import { isNative, ClockContext } from './chunk-L5XM5VKB.js';
1
+ import { clockSpecOf, isNative, ClockContext } from './chunk-TT4ZOYB6.js';
2
2
  import { isSafeOverride, storyNameFromExport, storyId, PROP_ARG_TYPES_EXPORT } from './chunk-5WS67HMU.js';
3
- import { portSafePart, appliedLocal, withUnsent, coverOf, ArgsContext } from './chunk-7XJNVJB5.js';
4
- import { useVisibleRaf, useLatest, isPlainObject } from '@weasel-js/core';
5
- import { createContext, useSyncExternalStore, useRef, useEffect, useState, Component, useLayoutEffect, useContext, createElement } from 'react';
6
- import { createEmitter } from '@weasel-js/registry';
3
+ import { portSafePart, appliedLocal, withUnsent, coverOf, ArgsContext } from './chunk-B26DWEYT.js';
4
+ import { DEFAULT_LAYOUT } from './chunk-STK2AZH2.js';
5
+ import { createContext, useState, useRef, useEffect, Component, useLayoutEffect, useContext, createElement } from 'react';
6
+ import { createClock, createClockRegistry } from '@weasel-js/clock';
7
+ import { useClockLoop } from '@weasel-js/clock/react';
8
+ import { useLatest, isPlainObject } from '@weasel-js/core';
7
9
  import { jsx } from 'react/jsx-runtime';
8
10
  import { f, UnionNode, ObjectNode } from '@weasel-js/labkit/config';
9
11
  import { iconFromData } from '@weasel-js/ui/components/Icon';
10
12
 
13
+ // src/frame/a11yContrast.ts
14
+ var RECHECKED = /* @__PURE__ */ new Set(["bgOverlap", "pseudoContent", "elmPartiallyObscured", "elmPartiallyObscuring"]);
15
+ var undecided = (key) => ({ decided: false, key });
16
+ function pseudoArea(el, which) {
17
+ const style = getComputedStyle(el, which);
18
+ if (style.content === "none" || style.display === "none" || style.visibility === "hidden" || style.position !== "absolute") return 0;
19
+ if (style.backgroundColor === "rgba(0, 0, 0, 0)" && style.backgroundImage === "none") return 0;
20
+ const width = parseFloat(style.width);
21
+ const height = parseFloat(style.height);
22
+ if (!style.width.endsWith("px") || !style.height.endsWith("px")) return width === 0 || height === 0 ? 0 : Infinity;
23
+ return width * height;
24
+ }
25
+ function hasPseudoBackdrop(node, root) {
26
+ const rect = node.getBoundingClientRect();
27
+ const minimum = rect.width * rect.height * 0.25;
28
+ for (let el = node; el; el = el.parentElement) {
29
+ if (pseudoArea(el, "::before") + pseudoArea(el, "::after") > minimum) return true;
30
+ if (el === root) return false;
31
+ }
32
+ return false;
33
+ }
34
+ function hasCompositing(node) {
35
+ for (let el = node; el; el = el.parentElement) {
36
+ const style = getComputedStyle(el);
37
+ if (Number(style.opacity) < 1 || style.filter !== "none" || style.mixBlendMode !== "normal") return true;
38
+ if (style.backdropFilter && style.backdropFilter !== "none") return true;
39
+ }
40
+ return false;
41
+ }
42
+ var isChrome = (el, root) => !root.contains(el) && !el.contains(root);
43
+ var paints = (style) => style.backgroundImage !== "none" || style.backgroundColor !== "rgba(0, 0, 0, 0)";
44
+ function stacksWithoutChrome(axe, node, root) {
45
+ const stacks = [];
46
+ for (const stack of axe.commons.dom.getTextElementStack(node)) {
47
+ const at = stack.indexOf(node);
48
+ const above = at < 0 ? stack : stack.slice(0, at);
49
+ if (above.some((el) => isChrome(el, root) && paints(getComputedStyle(el)))) return null;
50
+ const kept = [...above.filter((el) => !isChrome(el, root)), ...at < 0 ? [] : stack.slice(at)];
51
+ stacks.push(axe.commons.dom.reduceToElementsBelowFloating(kept, node));
52
+ }
53
+ return stacks;
54
+ }
55
+ function covers(el, style, rects) {
56
+ const box = el.getBoundingClientRect();
57
+ const scrolls = ["scroll", "auto"].includes(style.overflow);
58
+ const right = scrolls ? box.left + el.scrollWidth : box.right;
59
+ const bottom = scrolls ? box.top + el.scrollHeight : box.bottom;
60
+ return rects.every((r) => r.top >= box.top && r.bottom <= bottom && r.left >= box.left && r.right <= right);
61
+ }
62
+ function ownText(node) {
63
+ let text = "";
64
+ for (const child of node.childNodes) if (child.nodeType === Node.TEXT_NODE) text += child.textContent ?? "";
65
+ return text.trim();
66
+ }
67
+ function textRects(node) {
68
+ const range = document.createRange();
69
+ const rects = [];
70
+ for (const child of node.childNodes) {
71
+ if (child.nodeType !== Node.TEXT_NODE || !child.textContent?.trim()) continue;
72
+ range.selectNodeContents(child);
73
+ rects.push(...range.getClientRects());
74
+ }
75
+ return rects;
76
+ }
77
+ function judgeContrast(axe, node, root) {
78
+ const { Color, flattenColors, hasValidContrastRatio } = axe.commons.color;
79
+ const style = getComputedStyle(node);
80
+ if (hasPseudoBackdrop(node, root)) return undecided("pseudoContent");
81
+ if (style.textShadow !== "none") return undecided("complexTextShadows");
82
+ if (hasCompositing(node) || parseFloat(style.getPropertyValue("-webkit-text-stroke-width")) > 0) return undecided("blended");
83
+ const stacks = stacksWithoutChrome(axe, node, root);
84
+ if (!stacks?.length) return undecided("bgOverlap");
85
+ for (const stack of stacks) {
86
+ if (stack[0] !== node) return undecided("bgOverlap");
87
+ if (stack.length !== stacks[0].length || stack.some((el, i) => el !== stacks[0][i])) return undecided("elmPartiallyObscuring");
88
+ }
89
+ const rects = textRects(node);
90
+ const layers = [];
91
+ for (const el of stacks[0]) {
92
+ const elStyle = getComputedStyle(el);
93
+ if (elStyle.backgroundImage !== "none") return undecided(elStyle.backgroundImage.includes("gradient") ? "bgGradient" : "bgImage");
94
+ if (el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof SVGElement) return undecided("imgNode");
95
+ const color = new Color().parseString(elStyle.backgroundColor);
96
+ if (color.alpha === 0) continue;
97
+ if (elStyle.display !== "inline" && !covers(el, elStyle, rects)) return undecided("elmPartiallyObscured");
98
+ layers.push(color);
99
+ if (color.alpha === 1) break;
100
+ }
101
+ const white = new Color().parseString("#fff");
102
+ const bg = layers.reduceRight((backdrop, layer) => flattenColors(layer, backdrop), white);
103
+ const fg = new Color().parseString(style.getPropertyValue("-webkit-text-fill-color") || style.color);
104
+ const bold = parseFloat(style.fontWeight) >= 700 || style.fontWeight === "bold";
105
+ const verdict = hasValidContrastRatio(bg, fg, parseFloat(style.fontSize), bold);
106
+ if (Math.floor(verdict.contrastRatio * 100) === 100) return undecided("equalRatio");
107
+ if (!verdict.isValid && ownText(node).length === 1) return undecided("shortTextContent");
108
+ return {
109
+ decided: true,
110
+ isValid: verdict.isValid,
111
+ ratio: verdict.contrastRatio,
112
+ expected: verdict.expectedContrastRatio,
113
+ fg: fg.toHexString(),
114
+ bg: bg.toHexString()
115
+ };
116
+ }
117
+
11
118
  // src/frame/a11y.ts
12
119
  var IMPACTS = /* @__PURE__ */ new Set(["minor", "moderate", "serious", "critical"]);
13
- function finding(from) {
120
+ function reasonOf(node) {
121
+ if (node.reason) return node.reason;
122
+ const checks = [...node.any ?? [], ...node.all ?? [], ...node.none ?? []];
123
+ const said = checks.find((c) => c.data?.messageKey) ?? checks.find((c) => c.message);
124
+ if (!said) return void 0;
125
+ const key = said.data?.messageKey;
126
+ return { ...key ? { key } : {}, ...said.message ? { message: said.message } : {} };
127
+ }
128
+ function finding(from, withReasons = false) {
14
129
  const impact = from.impact ?? "";
130
+ const reasons = withReasons ? from.nodes.map(reasonOf) : [];
15
131
  return {
16
132
  id: from.id,
17
133
  impact: IMPACTS.has(impact) ? impact : null,
18
134
  help: from.help,
19
135
  helpUrl: from.helpUrl,
20
136
  description: from.description,
21
- nodes: from.nodes.map((node) => ({
137
+ nodes: from.nodes.map((node, i) => ({
22
138
  // An axe target entry is a selector, or an array of them for an element inside a shadow root.
23
139
  target: node.target.map((part) => Array.isArray(part) ? part.join(" ") : String(part)),
24
140
  html: node.html,
25
- ...node.failureSummary === void 0 ? {} : { failureSummary: node.failureSummary }
141
+ ...node.failureSummary === void 0 ? {} : { failureSummary: node.failureSummary },
142
+ ...reasons[i] ? { reason: reasons[i] } : {}
26
143
  }))
27
144
  };
28
145
  }
@@ -45,17 +162,49 @@ var last = Promise.resolve();
45
162
  async function runAxe(element, { page = true } = {}) {
46
163
  const axe = await loadAxe();
47
164
  const rules = page ? void 0 : Object.fromEntries(PAGE_RULES.map((id) => [id, { enabled: false }]));
48
- const run = () => axe.run(element, rules ? { rules } : void 0);
165
+ const run = async () => recheckContrast(axe, element, await axe.run(element, { elementRef: true, ...rules ? { rules } : {} }));
49
166
  const running = last.then(run, run);
50
167
  last = running;
51
168
  const result = await running;
52
169
  return {
53
- violations: result.violations.map(finding),
54
- incomplete: result.incomplete.map(finding),
170
+ violations: result.violations.map((f3) => finding(f3)),
171
+ incomplete: result.incomplete.map((f3) => finding(f3, true)),
55
172
  passes: result.passes.length,
56
173
  inapplicable: result.inapplicable.length
57
174
  };
58
175
  }
176
+ function recheckContrast(axe, root, result) {
177
+ const review = result.incomplete.find((f3) => f3.id === "color-contrast");
178
+ const rechecked = (n) => n.element !== void 0 && RECHECKED.has(reasonOf(n)?.key ?? "");
179
+ if (!review?.nodes.some(rechecked)) return result;
180
+ const kept = [];
181
+ const failed = [];
182
+ let passed = false;
183
+ axe.setup(document);
184
+ try {
185
+ for (const node of review.nodes) {
186
+ const verdict = rechecked(node) ? judgeContrast(axe, node.element, root) : null;
187
+ if (!verdict) kept.push(node);
188
+ else if (!verdict.decided) kept.push({ ...node, failureSummary: void 0, reason: { key: verdict.key } });
189
+ else if (verdict.isValid) passed = true;
190
+ else {
191
+ const summary = `Contrast is ${verdict.ratio.toFixed(2)}:1 (foreground ${verdict.fg}, background ${verdict.bg}); ${verdict.expected}:1 is needed.`;
192
+ failed.push({ ...node, failureSummary: summary });
193
+ }
194
+ }
195
+ } finally {
196
+ axe.teardown();
197
+ }
198
+ const others = (list) => list.filter((f3) => f3.id !== "color-contrast");
199
+ const failing = result.violations.find((f3) => f3.id === "color-contrast");
200
+ const violations = failed.length === 0 ? result.violations : [
201
+ ...others(result.violations),
202
+ { ...review, impact: "serious", ...failing, nodes: [...failing?.nodes ?? [], ...failed] }
203
+ ];
204
+ const incomplete = kept.length === 0 ? others(result.incomplete) : result.incomplete.map((f3) => f3 === review ? { ...f3, nodes: kept } : f3);
205
+ const passes = passed && !result.passes.some((p) => p.id === "color-contrast") ? [...result.passes, { id: "color-contrast" }] : result.passes;
206
+ return { ...result, violations, incomplete, passes };
207
+ }
59
208
  async function auditElement(element, options) {
60
209
  try {
61
210
  return { ok: true, report: await runAxe(element, options) };
@@ -332,138 +481,32 @@ function createGlobalsTarget(root, scope) {
332
481
  }
333
482
  };
334
483
  }
335
- var NEVER = () => () => {
336
- };
337
- var notPlaying = () => false;
338
- function useClockLoop(clock, target) {
339
- const playing = useSyncExternalStore(clock?.subscribe ?? NEVER, clock ? () => clock.get().playing : notPlaying);
340
- const last2 = useRef(null);
341
- const raf = useVisibleRaf(
342
- (now, raf2) => {
343
- if (!clock) return;
344
- const prev = last2.current;
345
- last2.current = now;
346
- if (prev !== null) clock.tick(now - prev);
347
- if (clock.get().playing) raf2.request();
348
- },
349
- {
350
- ...target ? { target } : {},
351
- onResume: () => {
352
- last2.current = null;
353
- }
354
- }
355
- );
356
- useEffect(() => {
357
- if (!playing) return;
358
- last2.current = null;
359
- raf.request();
360
- return () => raf.cancel();
361
- }, [playing, raf]);
362
- }
363
- function spanOf(spec) {
364
- const start = Number.isFinite(spec.start) ? spec.start : 0;
365
- const duration = Number.isFinite(spec.duration) && spec.duration > 0 ? spec.duration : 0;
366
- return { start, end: start + duration };
367
- }
368
- function clampTime(span, t) {
369
- if (Number.isNaN(t)) return span.start;
370
- return Math.min(span.end, Math.max(span.start, t));
371
- }
372
- function advance(state, elapsed) {
373
- if (!state.playing) return state;
374
- const { start, end } = state.span;
375
- const length = end - start;
376
- if (length <= 0) return { ...state, time: start, playing: false };
377
- const next = state.time + elapsed * state.rate;
378
- if (next <= end) return { ...state, time: next };
379
- if (!state.loop) return { ...state, time: end, playing: false };
380
- return { ...state, time: start + (next - start) % length };
381
- }
382
- var validRate = (rate) => typeof rate === "number" && Number.isFinite(rate) && rate > 0;
383
- var placed = (span, requested) => requested === null ? span.start : clampTime(span, requested);
384
- function createClock(spec, initial) {
385
- let declared = spanOf(spec);
386
- let override = null;
387
- let requested = { time: initial ?? null };
388
- let state = {
389
- time: placed(declared, requested.time),
390
- playing: false,
391
- loop: spec.loop ?? true,
392
- rate: validRate(spec.rate) ? spec.rate : 1,
393
- span: declared
394
- };
395
- const changed = createEmitter();
396
- const pendingIn = (at) => requested && requested.time !== null && requested.time !== at.time ? requested.time : null;
397
- let pending = pendingIn(state);
398
- const set = (next) => {
399
- const nextPending = pendingIn(next);
400
- if (nextPending === pending && next.time === state.time && next.playing === state.playing && next.loop === state.loop && next.rate === state.rate && next.span.start === state.span.start && next.span.end === state.span.end)
401
- return;
402
- state = next;
403
- pending = nextPending;
404
- changed.emit();
405
- };
406
- const moved = (next) => {
407
- requested = null;
408
- set(next);
409
- };
410
- const respan = () => {
411
- const span = override ?? declared;
412
- set({ ...state, span, time: requested ? placed(span, requested.time) : clampTime(span, state.time) });
413
- };
414
- return {
415
- get: () => state,
416
- subscribe: changed.subscribe,
417
- play() {
418
- if (state.playing) return;
419
- moved({ ...state, playing: true, time: state.time >= state.span.end ? state.span.start : state.time });
420
- },
421
- pause: () => set({ ...state, playing: false }),
422
- seek: (time) => moved({ ...state, time: clampTime(state.span, time) }),
423
- request(time) {
424
- requested = { time: Number.isNaN(time) ? null : time };
425
- set({ ...state, playing: false, time: placed(state.span, requested.time) });
426
- },
427
- pending: () => pending,
428
- setLoop: (loop) => set({ ...state, loop }),
429
- setRate(rate) {
430
- if (validRate(rate)) set({ ...state, rate });
431
- },
432
- setSpan(span) {
433
- override = span ? spanOf(span) : null;
434
- respan();
435
- },
436
- declare(next) {
437
- declared = spanOf(next);
438
- respan();
439
- },
440
- tick(elapsed) {
441
- if (state.playing) moved(advance(state, elapsed));
442
- }
443
- };
444
- }
445
- function useStoryClock(spec, initial) {
446
- const [clock, setClock] = useState(() => spec ? createClock(spec, initial()) : null);
447
- let current = clock;
448
- if (spec && !clock) {
449
- current = createClock(spec, initial());
450
- setClock(current);
451
- } else if (!spec && clock) {
484
+ var keyOf = (spec) => spec ? `${spec.start ?? 0}|${spec.duration}|${spec.loop ?? true}|${spec.rate ?? 1}` : null;
485
+ function useOwnClock(spec) {
486
+ const [handle, setHandle] = useState(() => spec ? createClock(clockSpecOf(spec)) : null);
487
+ let current = handle;
488
+ if (spec && !handle) {
489
+ current = createClock(clockSpecOf(spec));
490
+ setHandle(current);
491
+ } else if (!spec && handle) {
452
492
  current = null;
453
- setClock(null);
493
+ setHandle(null);
454
494
  }
455
495
  const latest = useLatest(spec);
456
- const spanKey = spec ? `${spec.start ?? 0}:${spec.duration}` : null;
496
+ const key = keyOf(spec);
497
+ const declared = useRef(key);
457
498
  useEffect(() => {
458
- if (current && latest.current) current.declare(latest.current);
459
- }, [current, spanKey, latest]);
499
+ if (!current || !latest.current || key === declared.current) return;
500
+ declared.current = key;
501
+ current.declare(clockSpecOf(latest.current));
502
+ }, [current, key, latest]);
460
503
  return current;
461
504
  }
462
- var NO_INITIAL = () => null;
463
505
  function OwnClock({ spec, children }) {
464
- const clock = useStoryClock(spec, NO_INITIAL);
465
- useClockLoop(clock);
466
- return /* @__PURE__ */ jsx(ClockContext.Provider, { value: clock, children });
506
+ const handle = useOwnClock(spec);
507
+ const [registry] = useState(() => createClockRegistry(handle));
508
+ useClockLoop(registry);
509
+ return /* @__PURE__ */ jsx(ClockContext.Provider, { value: handle, children });
467
510
  }
468
511
  function WithStoryClock({ story, config, children }) {
469
512
  const outer = useContext(ClockContext);
@@ -811,7 +854,7 @@ function loadCsfModule(mod, title, projectParameters = {}) {
811
854
  return scoped(ctx, (context) => renderFn(context.args, context));
812
855
  },
813
856
  decorators: [...asArray(spec.decorators), ...asArray(meta.decorators)].map(adapt),
814
- layout: layout && LAYOUTS.includes(layout) ? layout : "padded",
857
+ layout: layout && LAYOUTS.includes(layout) ? layout : DEFAULT_LAYOUT,
815
858
  viewport: resolveViewport(parameters, storyGlobals),
816
859
  isolate: typeof isolate === "string" ? isolate : null,
817
860
  play: play ? ({ canvasElement, config: config2, globals }) => play({ ...csfContext(config2, globals), canvasElement, step: async (_label, fn) => fn() }) : null,
@@ -835,7 +878,7 @@ function loadNativeModule(mod, title) {
835
878
  initialState: spec.state ?? null,
836
879
  render: spec.render,
837
880
  decorators: [...spec.decorators ?? [], ...metaSpec.decorators ?? []],
838
- layout: spec.layout ?? metaSpec.layout ?? "centered",
881
+ layout: spec.layout ?? metaSpec.layout ?? DEFAULT_LAYOUT,
839
882
  viewport: spec.viewport ?? null,
840
883
  isolate: spec.isolate ?? metaSpec.isolate ?? null,
841
884
  play: spec.play ?? null,
@@ -854,49 +897,6 @@ function indexRenderOf(mod) {
854
897
  const render = isNative(meta, "meta") ? meta?.index : meta?.parameters?.forge?.index;
855
898
  return typeof render === "function" ? render : null;
856
899
  }
857
- var IN_VIEW_MARGIN = "50%";
858
- function useNearViewport(ref, margin, initial = true) {
859
- const [near, setNear] = useState(() => initial || typeof IntersectionObserver === "undefined");
860
- useEffect(() => {
861
- const el = ref.current;
862
- if (!el || typeof IntersectionObserver === "undefined") return;
863
- const recheck = () => {
864
- observer.unobserve(el);
865
- observer.observe(el);
866
- };
867
- let watching = false;
868
- const watch = (on) => {
869
- if (on === watching) return;
870
- watching = on;
871
- if (on) {
872
- document.addEventListener("scroll", recheck, { capture: true, passive: true });
873
- window.addEventListener("resize", recheck);
874
- } else {
875
- document.removeEventListener("scroll", recheck, { capture: true });
876
- window.removeEventListener("resize", recheck);
877
- }
878
- };
879
- const observer = new IntersectionObserver(
880
- (entries) => {
881
- const last2 = entries.at(-1);
882
- if (!last2) return;
883
- const root = last2.rootBounds;
884
- const box = last2.boundingClientRect;
885
- setNear(
886
- root ? box.bottom >= root.top && box.top <= root.bottom && box.right >= root.left && box.left <= root.right : last2.isIntersecting
887
- );
888
- watch(Boolean(root) && !last2.isIntersecting);
889
- },
890
- { root: document, rootMargin: margin }
891
- );
892
- observer.observe(el);
893
- return () => {
894
- observer.disconnect();
895
- watch(false);
896
- };
897
- }, [ref, margin]);
898
- return near;
899
- }
900
900
  var scopeOf = (id) => `[data-fg-host="${id}"]`;
901
901
  function applyGlobalsTo(host, hostId, globals, setup) {
902
902
  const target = createGlobalsTarget(host, scopeOf(hostId));
@@ -935,6 +935,6 @@ function useHostConditions(host, opts) {
935
935
  return overridesRef;
936
936
  }
937
937
 
938
- export { IN_VIEW_MARGIN, StoryHost, applyGlobalsTo, auditElement, createA11yHighlighter, createGlobalsTarget, createOverrides, cssRulesOf, indexRenderOf, loadStories, resolveCssVar, runAxe, scanCssVars, useClockLoop, useHostConditions, useNearViewport, useStoryClock, varRefs };
939
- //# sourceMappingURL=chunk-GHPMNT2A.js.map
940
- //# sourceMappingURL=chunk-GHPMNT2A.js.map
938
+ export { StoryHost, applyGlobalsTo, auditElement, createA11yHighlighter, createGlobalsTarget, createOverrides, cssRulesOf, indexRenderOf, loadStories, resolveCssVar, runAxe, scanCssVars, useHostConditions, useOwnClock, varRefs };
939
+ //# sourceMappingURL=chunk-PGLOYXDP.js.map
940
+ //# sourceMappingURL=chunk-PGLOYXDP.js.map