@weasel-js/forge 1.16.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -406,4 +406,5 @@ declare function startFrame({ story, channel, container, setup }: StartFrameOpti
406
406
  /** Reports an import failure — there is no story to start. */
407
407
  declare function reportImportFault(channel: Channel<ToFrame, FromFrame>, error: unknown): void;
408
408
 
409
- 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 };
409
+ export { reportImportFault as r, startFrame as s };
410
+ export type { A11yFinding as A, Channel as C, DepGraph as D, FrameSetup as F, Globals as G, IndexEntry as I, Layout as L, MetaSpec as M, PlayContext as P, StartFrameOptions as S, ToFrame as T, Viewport as V, ConfigAnswers as a, FaultPhase as b, FromFrame as c, StorySpec as d, Span as e, SpanOverride as f, A11yNode as g, A11yReport as h, ComponentDeps as i, ComponentHistory as j, Decorator as k, GlobalsTarget as l, IndexContext as m, IndexRender as n, IndexStoryProps as o, LoadedStory as p, StoryContext as q, TimelineSpec as t, CssVarReport as u, A11yOutcome as v, DescribedEntry as w, DescribedShape as x, SchemaDescription as y, SectionDescription as z };
@@ -110,7 +110,7 @@ function knobPaths(schema) {
110
110
  if (key.startsWith(INTERNAL_PREFIX) || key.includes(".")) continue;
111
111
  const path = at === "" ? key : `${at}.${key}`;
112
112
  if (isConfigBranch(entry)) walk(entry.children, path);
113
- else if (codecOf(entry) !== null) out.push({ path, leaf: entry });
113
+ else if (codecOf(entry) !== null && isKnobParam(path)) out.push({ path, leaf: entry });
114
114
  }
115
115
  };
116
116
  walk(schema.nodes, "");
@@ -133,7 +133,7 @@ function paramsToKnobs(schema, params) {
133
133
  const knobs = {};
134
134
  for (const { path, leaf } of knobPaths(schema)) {
135
135
  const raw = params[path];
136
- if (raw === void 0 || !isKnobParam(path)) continue;
136
+ if (raw === void 0) continue;
137
137
  const decoded = decodeKnob(raw, leaf);
138
138
  if (decoded) knobs[path] = decoded.value;
139
139
  }
@@ -170,5 +170,5 @@ function paramsToPins(schema, params) {
170
170
  }
171
171
 
172
172
  export { PLAYHEAD_PARAM, RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, paramsToPins, parseRoute, pinParams, reservedParams, sameKnob, storyHref, withoutPins };
173
- //# sourceMappingURL=chunk-SFKDHXVE.js.map
174
- //# sourceMappingURL=chunk-SFKDHXVE.js.map
173
+ //# sourceMappingURL=chunk-MUBZSDOI.js.map
174
+ //# sourceMappingURL=chunk-MUBZSDOI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/route/url.ts"],"names":[],"mappings":"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","file":"chunk-MUBZSDOI.js","sourcesContent":["import { type ConfigSchema, type ConfigShape, isConfigBranch, valueAtPath } from '@weasel-js/labkit/config';\nimport { isPlainObject } from '@weasel-js/core';\nimport { type Globals, stableStringify } from '../protocol/messages';\nimport { GLOBALS_KEY } from '../shell/globals';\n\n/**\n * Forge's URL, as pure functions: `#/<story id>?<params>`. The story and its params share the hash, so the page's\n * own query string stays the host's. A param is a knob — a story arg, addressed by its dotted config path\n * (`look.px`) — unless its name is reserved for the workshop itself.\n */\n\n/** The param a paused playhead is held in, in seconds. */\nexport const PLAYHEAD_PARAM = 't';\n\n/** Param names that are never knobs. */\nexport const RESERVED_PARAMS: ReadonlySet<string> = new Set([PLAYHEAD_PARAM]);\n\n/** Config keys forge keeps for itself, such as the `$globals` pins, start with this and never reach the URL. */\nconst INTERNAL_PREFIX = '$';\n\n/** What a forge hash holds: the story it names, and every param after it, knobs and reserved alike, in order. */\nexport interface ForgeRoute {\n story: string | null;\n params: Readonly<Record<string, string>>;\n}\n\n/** Reads a hash (`location.hash`, with or without its `#`). Anything not in the `#/<id>` form names no story. */\nexport function parseRoute(hash: string): ForgeRoute {\n const body = hash.startsWith('#') ? hash.slice(1) : hash;\n if (!body.startsWith('/')) return { story: null, params: {} };\n const at = body.indexOf('?');\n const path = at === -1 ? body.slice(1) : body.slice(1, at);\n let story: string | null;\n try {\n story = decodeURIComponent(path) || null;\n } catch {\n story = null;\n }\n const params: Record<string, string> = {};\n if (at !== -1) for (const [key, value] of new URLSearchParams(body.slice(at + 1))) params[key] = value;\n return { story, params };\n}\n\n/** The hash for `route`, with its leading `#`. The inverse of `parseRoute`. */\nexport function formatRoute(route: ForgeRoute): string {\n const query = new URLSearchParams(Object.entries(route.params)).toString();\n return `#/${encodeURIComponent(route.story ?? '')}${query ? `?${query}` : ''}`;\n}\n\n/** Whether a param name can be a knob: not reserved and not one of forge's own config keys. */\nexport function isKnobParam(name: string): boolean {\n return name !== '' && !RESERVED_PARAMS.has(name) && !name.startsWith(INTERNAL_PREFIX);\n}\n\n/** The params that are knobs, with the reserved ones left out. */\nexport function knobParams(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => isKnobParam(name)));\n}\n\n/** The params that are not knobs. Kept as they are when the knobs are rewritten. */\nexport function reservedParams(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => !isKnobParam(name)));\n}\n\n/** What a knob's value is decoded against: its schema leaf's declared kind, its default, and its choices. */\nexport interface KnobLeaf {\n kind: string | null;\n default: unknown;\n annotations?: { options?: readonly { value: string }[] };\n}\n\ntype Codec = 'number' | 'boolean' | 'string' | 'enum' | 'json';\n\n/** How a leaf's value travels: by its declared kind first, then by the type of its default. */\nfunction codecOf(leaf: KnobLeaf): Codec | null {\n switch (leaf.kind) {\n case 'number':\n case 'boolean':\n case 'string':\n case 'enum':\n return leaf.kind;\n case 'color':\n return 'string';\n case 'list':\n case 'json':\n return 'json';\n }\n const d = leaf.default;\n if (typeof d === 'number' || typeof d === 'boolean' || typeof d === 'string') return typeof d as Codec;\n return Array.isArray(d) || isPlainObject(d) ? 'json' : null;\n}\n\n/** A value as one param. Undefined for a value no param can carry, such as a function. */\nexport function encodeKnob(value: unknown): string | undefined {\n switch (typeof value) {\n case 'string':\n return value;\n case 'number':\n case 'boolean':\n return String(value);\n case 'object':\n return value !== null && (Array.isArray(value) || isPlainObject(value)) ? JSON.stringify(value) : undefined;\n default:\n return undefined;\n }\n}\n\n/** Whether `value` is something a leaf of codec `codec` could hold, so a well-formed param of the wrong type is refused. */\nfunction fits(codec: Codec, value: unknown, leaf: KnobLeaf): boolean {\n if (codec !== 'json') return true;\n const d = leaf.default;\n if (Array.isArray(d)) {\n if (!Array.isArray(value)) return false;\n return leaf.kind !== 'list' || value.every((item) => typeof item === 'string');\n }\n if (isPlainObject(d)) return isPlainObject(value);\n return true;\n}\n\n/**\n * One param read as `leaf`'s type: `{ value }`, or undefined for a param that does not parse as one — a number that\n * is not a number, an option the enum does not list, malformed JSON. A bare boolean param (`?dark`) reads as true.\n */\nexport function decodeKnob(raw: string, leaf: KnobLeaf): { value: unknown } | undefined {\n const codec = codecOf(leaf);\n switch (codec) {\n case 'number': {\n const n = raw.trim() === '' ? Number.NaN : Number(raw);\n return Number.isNaN(n) ? undefined : { value: n };\n }\n case 'boolean':\n if (raw === '' || raw === 'true' || raw === '1') return { value: true };\n if (raw === 'false' || raw === '0') return { value: false };\n return undefined;\n case 'string':\n return { value: raw };\n case 'enum': {\n const options = leaf.annotations?.options;\n return !options || options.some((o) => o.value === raw) ? { value: raw } : undefined;\n }\n case 'json': {\n try {\n const value: unknown = JSON.parse(raw);\n return fits(codec, value, leaf) ? { value } : undefined;\n } catch {\n return undefined;\n }\n }\n default:\n return undefined;\n }\n}\n\n/** One leaf a URL can address: its dotted path, and what its value decodes against. */\nexport interface KnobPath {\n path: string;\n leaf: KnobLeaf;\n}\n\n/** Every leaf of `schema` a param can carry, in schema order, reserved names left out, with branches flattened into dotted paths. */\nexport function knobPaths(schema: Pick<ConfigSchema<unknown>, 'nodes'>): KnobPath[] {\n const out: KnobPath[] = [];\n const walk = (shape: ConfigShape, at: string): void => {\n for (const [key, entry] of Object.entries(shape)) {\n if (key.startsWith(INTERNAL_PREFIX) || key.includes('.')) continue;\n const path = at === '' ? key : `${at}.${key}`;\n if (isConfigBranch(entry)) walk(entry.children, path);\n else if (codecOf(entry) !== null && isKnobParam(path)) out.push({ path, leaf: entry });\n }\n };\n walk(schema.nodes, '');\n return out;\n}\n\n/** Whether two knob values are the same value, compared as data. */\nexport function sameKnob(a: unknown, b: unknown): boolean {\n return Object.is(a, b) || stableStringify(a) === stableStringify(b);\n}\n\n/**\n * The knob params for `config`: one per leaf whose value differs from its default, keyed by dotted path, in schema\n * order. A leaf absent from `config` is at its default.\n */\nexport function knobsToParams(schema: Pick<ConfigSchema<unknown>, 'nodes'>, config: unknown): Record<string, string> {\n const params: Record<string, string> = {};\n for (const { path, leaf } of knobPaths(schema)) {\n const value = valueAtPath(config, path);\n if (value === undefined || sameKnob(value, leaf.default)) continue;\n const encoded = encodeKnob(value);\n if (encoded !== undefined) params[path] = encoded;\n }\n return params;\n}\n\n/**\n * The knob values `params` sets, by dotted path, each coerced to its leaf's type. A param naming no leaf, a reserved\n * param, and one that does not parse as its leaf's type are left out rather than failing the rest.\n */\nexport function paramsToKnobs(\n schema: Pick<ConfigSchema<unknown>, 'nodes'>,\n params: Readonly<Record<string, string>>,\n): Record<string, unknown> {\n const knobs: Record<string, unknown> = {};\n for (const { path, leaf } of knobPaths(schema)) {\n const raw = params[path];\n if (raw === undefined) continue;\n const decoded = decodeKnob(raw, leaf);\n if (decoded) knobs[path] = decoded.value;\n }\n return knobs;\n}\n\n/**\n * A link to a story at a given state, for a host that has no schema to hand: each knob is a dotted path and a value,\n * encoded by its own type. `params` adds workshop params, such as `t`.\n */\nexport function storyHref(\n story: string,\n knobs: Readonly<Record<string, unknown>> = {},\n params: Readonly<Record<string, string>> = {},\n): string {\n const encoded: Record<string, string> = {};\n for (const [path, value] of Object.entries(knobs)) {\n const raw = encodeKnob(value);\n if (raw !== undefined && isKnobParam(path)) encoded[path] = raw;\n }\n return formatRoute({ story, params: { ...encoded, ...reservedParams(params) } });\n}\n\n/** A param pinning one of the trial's globals is named by the pin's config path, `$globals.<key>`. */\nconst PIN_PREFIX = `${GLOBALS_KEY}.`;\n\n/** The params pinning a trial's globals to `globals`. Not knobs: they are read once, as the story opens, and dropped. */\nexport function pinParams(globals: Globals): Record<string, string> {\n const params: Record<string, string> = {};\n for (const [key, value] of Object.entries(globals)) if (typeof value === 'string') params[`${PIN_PREFIX}${key}`] = value;\n return params;\n}\n\n/** `params` without the ones pinning globals. */\nexport function withoutPins(params: Readonly<Record<string, string>>): Record<string, string> {\n return Object.fromEntries(Object.entries(params).filter(([name]) => !name.startsWith(PIN_PREFIX)));\n}\n\n/** The pins `params` sets, by config path: each a value its global's leaf in `schema` offers; any other is left out. */\nexport function paramsToPins(\n schema: Pick<ConfigSchema<unknown>, 'nodes'>,\n params: Readonly<Record<string, string>>,\n): Record<string, string> {\n const group = (schema.nodes as ConfigShape)[GLOBALS_KEY];\n if (!group || !isConfigBranch(group)) return {};\n const pins: Record<string, string> = {};\n for (const [key, leaf] of Object.entries(group.children)) {\n const raw = params[`${PIN_PREFIX}${key}`];\n if (raw === undefined || isConfigBranch(leaf)) continue;\n const decoded = decodeKnob(raw, leaf);\n if (typeof decoded?.value === 'string') pins[`${PIN_PREFIX}${key}`] = decoded.value;\n }\n return pins;\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { LabMode, LabContribution, LabPage } from '@weasel-js/labkit';
2
2
  import { Theme } from '@weasel-js/theme';
3
3
  import { ControlRenderer } from '@weasel-js/labkit/config';
4
- import { G as Globals, F as FrameSetup } from './FrameController-D9CddmJg.js';
4
+ import { G as Globals, F as FrameSetup } from './FrameController-DQTN1ytP.js';
5
5
  import { IconRef } from '@weasel-js/icons';
6
6
  import { L as Library } from './library-CdTj-hFp.js';
7
7
 
@@ -83,4 +83,5 @@ declare function defineFrameConfig(config: FrameSetup): FrameSetup;
83
83
  /** A shell config module's default export. */
84
84
  declare function defineShellConfig(config: ShellConfig): ShellConfig;
85
85
 
86
- export { FOLLOW_APP as F, type GlobalDeclaration as G, type LabChrome as L, type ShellConfig as S, type GlobalDeclarations as a, defineShellConfig as b, defineFrameConfig as d };
86
+ export { FOLLOW_APP as F, defineShellConfig as b, defineFrameConfig as d };
87
+ export type { GlobalDeclaration as G, LabChrome as L, ShellConfig as S, GlobalDeclarations as a };
@@ -1,9 +1,11 @@
1
- import { G as Globals, L as Layout, I as IndexEntry, F as FrameSetup, D as DepGraph } from '../FrameController-D9CddmJg.js';
2
- export { C as Channel, a as ConfigAnswers, b as FaultPhase, c as FromFrame, S as StartFrameOptions, T as ToFrame, r as reportImportFault, s as startFrame } from '../FrameController-D9CddmJg.js';
3
- import { F as FrameImporters } from '../mountFrame-AZzyw6bu.js';
4
- export { a as FrameIndexEntry, M as MountFrameOptions, m as mountFrame } from '../mountFrame-AZzyw6bu.js';
1
+ import { F as FrameSetup } from '../FrameController-DQTN1ytP.js';
2
+ import { StoryGlobals as Globals, StoryLayout as Layout, DepGraph } from '../index.js';
3
+ import { IndexEntry } from '../shell/index.js';
4
+ export { C as Channel, a as ConfigAnswers, b as FaultPhase, c as FromFrame, S as StartFrameOptions, T as ToFrame, r as reportImportFault, s as startFrame } from '../FrameController-DQTN1ytP.js';
5
+ import { F as FrameImporters } from '../mountFrame-CrIdX6nD.js';
6
+ export { a as FrameIndexEntry, M as MountFrameOptions, m as mountFrame } from '../mountFrame-CrIdX6nD.js';
5
7
  import { Theme } from '@weasel-js/theme';
6
- import { L as Library } from '../library-CdTj-hFp.js';
8
+ import { Library } from '../index.js';
7
9
  import '@weasel-js/icons';
8
10
  import '@weasel-js/labkit/config';
9
11
  import 'react';
@@ -75,4 +77,5 @@ declare global {
75
77
  */
76
78
  declare function mountCapture(options: MountCaptureOptions): void;
77
79
 
78
- export { FrameImporters, FrameSetup, type MountCaptureOptions, mountCapture };
80
+ export { FrameImporters, FrameSetup, mountCapture };
81
+ export type { MountCaptureOptions };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ConfigSchema } from '@weasel-js/labkit/config';
2
2
  export { f } from '@weasel-js/labkit/config';
3
- export { F as FOLLOW_APP, G as GlobalDeclaration, a as GlobalDeclarations, L as LabChrome, S as ShellConfig, d as defineFrameConfig, b as defineShellConfig } from './config-Ey2RPFrg.js';
3
+ export { F as FOLLOW_APP, G as GlobalDeclaration, a as GlobalDeclarations, L as LabChrome, S as ShellConfig, d as defineFrameConfig, b as defineShellConfig } from './config-BaC0Cbyj.js';
4
4
  import { ComponentType, ReactNode } from 'react';
5
- import { G as Globals, M as MetaSpec, d as StorySpec, e as Span, f as SpanOverride } from './FrameController-D9CddmJg.js';
6
- export { A as A11yFinding, g as A11yNode, h as A11yReport, i as ComponentDeps, j as ComponentHistory, k as Decorator, D as DepGraph, F as FrameSetup, l as GlobalsTarget, m as IndexContext, n as IndexRender, o as IndexStoryProps, p as LoadedStory, P as PlayContext, q as StoryContext, L as StoryLayout, V as StoryViewport, t as TimelineSpec } from './FrameController-D9CddmJg.js';
5
+ import { G as Globals, M as MetaSpec, d as StorySpec, e as Span, f as SpanOverride } from './FrameController-DQTN1ytP.js';
6
+ export { A as A11yFinding, g as A11yNode, h as A11yReport, i as ComponentDeps, j as ComponentHistory, k as Decorator, D as DepGraph, F as FrameSetup, l as GlobalsTarget, m as IndexContext, n as IndexRender, o as IndexStoryProps, p as LoadedStory, P as PlayContext, q as StoryContext, L as StoryLayout, V as StoryViewport, t as TimelineSpec } from './FrameController-DQTN1ytP.js';
7
7
  export { L as Library } from './library-CdTj-hFp.js';
8
8
  import '@weasel-js/labkit';
9
9
  import '@weasel-js/theme';
@@ -167,7 +167,7 @@ interface KnobPath {
167
167
  path: string;
168
168
  leaf: KnobLeaf;
169
169
  }
170
- /** Every leaf of `schema` a param can carry, in schema order, with branches flattened into dotted paths. */
170
+ /** Every leaf of `schema` a param can carry, in schema order, reserved names left out, with branches flattened into dotted paths. */
171
171
  declare function knobPaths(schema: Pick<ConfigSchema<unknown>, 'nodes'>): KnobPath[];
172
172
  /**
173
173
  * The knob params for `config`: one per leaf whose value differs from its default, keyed by dotted path, in schema
@@ -218,4 +218,5 @@ declare const GALLERY_TAG = "gallery";
218
218
  /** The meta tag marking a story file as a lab: a whole screen for exercising a component, not a specimen of it. */
219
219
  declare const LAB_TAG = "lab";
220
220
 
221
- export { type ArgCondition, type ArgShape, type ArgType, type ArgsOf, type CsfDecorator, type CsfPlayContext, type CsfStep, type CsfStoryContext, type ForgeRoute, GALLERY_TAG, type KnobLeaf, type KnobPath, LAB_TAG, type Meta, MetaSpec, RESERVED_PARAMS, SpanOverride, Globals as StoryGlobals, type StoryObj, StorySpec, type StoryTimeline, Span as TimelineSpan, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, meta, paramsToKnobs, parseRoute, reservedParams, story, storyHref, usePlayhead, useTimeline };
221
+ export { GALLERY_TAG, LAB_TAG, MetaSpec, RESERVED_PARAMS, SpanOverride, Globals as StoryGlobals, StorySpec, Span as TimelineSpan, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, meta, paramsToKnobs, parseRoute, reservedParams, story, storyHref, usePlayhead, useTimeline };
222
+ export type { ArgCondition, ArgShape, ArgType, ArgsOf, CsfDecorator, CsfPlayContext, CsfStep, CsfStoryContext, ForgeRoute, KnobLeaf, KnobPath, Meta, StoryObj, StoryTimeline };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, parseRoute, reservedParams, storyHref } from './chunk-SFKDHXVE.js';
1
+ export { RESERVED_PARAMS, decodeKnob, encodeKnob, formatRoute, knobParams, knobPaths, knobsToParams, paramsToKnobs, parseRoute, reservedParams, storyHref } from './chunk-MUBZSDOI.js';
2
2
  export { GALLERY_TAG, LAB_TAG } from './chunk-VUPN73JP.js';
3
3
  export { FOLLOW_APP } from './chunk-FPF2JSWB.js';
4
4
  export { meta, story, usePlayhead, useTimeline } from './chunk-TT4ZOYB6.js';
@@ -1,4 +1,4 @@
1
- import { F as FrameSetup, p as LoadedStory } from './FrameController-D9CddmJg.js';
1
+ import { F as FrameSetup, p as LoadedStory } from './FrameController-DQTN1ytP.js';
2
2
 
3
3
  /** Story modules by file path, each loaded on first use. */
4
4
  interface FrameImporters {
@@ -32,4 +32,5 @@ interface MountFrameOptions {
32
32
  */
33
33
  declare function mountFrame(options: MountFrameOptions): Promise<void>;
34
34
 
35
- export { type FrameImporters as F, type MountFrameOptions as M, type FrameIndexEntry as a, mountFrame as m };
35
+ export { mountFrame as m };
36
+ export type { FrameImporters as F, MountFrameOptions as M, FrameIndexEntry as a };
@@ -1,10 +1,12 @@
1
1
  import * as react from 'react';
2
2
  import { RefObject, ReactNode } from 'react';
3
3
  import { LabContribution, StorageAdapter, RenderContext, InstrumentList } from '@weasel-js/labkit';
4
- import { F as FrameImporters } from '../mountFrame-AZzyw6bu.js';
5
- import { I as IndexEntry, F as FrameSetup, D as DepGraph, p as LoadedStory, L as Layout, G as Globals, c as FromFrame, T as ToFrame, u as CssVarReport, v as A11yOutcome } from '../FrameController-D9CddmJg.js';
6
- export { w as DescribedEntry, x as DescribedShape, y as SchemaDescription, z as SectionDescription } from '../FrameController-D9CddmJg.js';
7
- import { S as ShellConfig, a as GlobalDeclarations } from '../config-Ey2RPFrg.js';
4
+ import { FrameImporters } from '../frame/index.js';
5
+ import { I as IndexEntry, u as CssVarReport, v as A11yOutcome } from '../FrameController-DQTN1ytP.js';
6
+ import { FrameSetup, FromFrame, ToFrame } from '../frame/index.js';
7
+ import { DepGraph, LoadedStory, StoryLayout as Layout, StoryGlobals as Globals } from '../index.js';
8
+ export { w as DescribedEntry, x as DescribedShape, y as SchemaDescription, z as SectionDescription } from '../FrameController-DQTN1ytP.js';
9
+ import { ShellConfig, GlobalDeclarations } from '../index.js';
8
10
  import { a as Libraries } from '../library-CdTj-hFp.js';
9
11
  import '@weasel-js/icons';
10
12
  import '@weasel-js/labkit/config';
@@ -220,4 +222,5 @@ declare const TrialFramesContext: react.Context<TrialFrames | null>;
220
222
  /** The frame of the trial `trialId` names, re-read when it connects or reports. */
221
223
  declare function useTrialFrame(trialId: string): TrialFrame;
222
224
 
223
- export { A11Y_SECTION, A11yOutcome, A11yPanel, CssVarReport, type FollowedEdits, type HotEvents, IndexEntry, Libraries, type MountWorkshopOptions, type MountedWorkshop, type StoryChanges, StoryGlobalsContext, type StoryRegistry, type StoryRegistryOptions, StoryTrial, type StoryTrialProps, type TrialFrame, type TrialFrameCalls, type TrialFrames, TrialFramesContext, TrialHost, type TrialHostProps, Workshop, type WorkshopProps, followEdits, mountWorkshop, useStoryRegistry, useTrialFrame };
225
+ export { A11Y_SECTION, A11yOutcome, A11yPanel, CssVarReport, IndexEntry, Libraries, StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, Workshop, followEdits, mountWorkshop, useStoryRegistry, useTrialFrame };
226
+ export type { FollowedEdits, HotEvents, MountWorkshopOptions, MountedWorkshop, StoryChanges, StoryRegistry, StoryRegistryOptions, StoryTrialProps, TrialFrame, TrialFrameCalls, TrialFrames, TrialHostProps, WorkshopProps };
@@ -1,4 +1,4 @@
1
- import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, paramsToPins, knobsToParams, PLAYHEAD_PARAM, pinParams, withoutPins, reservedParams } from '../chunk-SFKDHXVE.js';
1
+ import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, paramsToPins, knobsToParams, PLAYHEAD_PARAM, pinParams, withoutPins, reservedParams } from '../chunk-MUBZSDOI.js';
2
2
  import { DEFAULT_TITLE, ISSUES_PATH, CAPTURE_PATH, issueLabel, messageOf, MIN_DPR, MAX_DPR } from '../chunk-7YVJS4QS.js';
3
3
  import { TITLE_LIBRARIES, loadIndexBundle, librariesFrom, LibrariesContext, LAB_DENSITY, createKitRoot, schemaFromDescription, LibraryBadge, Description, IndexPage, indexEnvOf, LabBadge, openChannel } from '../chunk-5B4RDEF3.js';
4
4
  import { isIndexId, indexId, indexEntries } from '../chunk-ZGELUE2H.js';
@@ -1,4 +1,4 @@
1
- import { F as FrameSetup } from '../FrameController-D9CddmJg.js';
1
+ import { FrameSetup } from '../frame/index.js';
2
2
  import '@weasel-js/icons';
3
3
  import '@weasel-js/labkit/config';
4
4
  import 'react';
@@ -16,4 +16,5 @@ declare function runStory(mod: Record<string, unknown>, exportName: string, file
16
16
  /** The file's index entry title. */
17
17
  title: string, { setup }?: RunStoryOptions): Promise<void>;
18
18
 
19
- export { type RunStoryOptions, runStory };
19
+ export { runStory };
20
+ export type { RunStoryOptions };
@@ -83,4 +83,5 @@ interface WatchRecoveryOptions {
83
83
  */
84
84
  declare function watchRecovery({ quietMs }?: WatchRecoveryOptions): Plugin;
85
85
 
86
- export { type ForgeOptions, type ForgeTestOptions, type PropControlsOptions, type WatchProbeOptions, type WatchRecoveryOptions, forge, forgeTest, watchProbe, watchRecovery };
86
+ export { forge, forgeTest, watchProbe, watchRecovery };
87
+ export type { ForgeOptions, ForgeTestOptions, PropControlsOptions, WatchProbeOptions, WatchRecoveryOptions };
@@ -122,19 +122,18 @@ var fileOfKey = (key) => key.slice(0, key.lastIndexOf("#"));
122
122
  var SCRIPT = /\.[cm]?[jt]sx?$/;
123
123
  var DEFAULT_LOCAL = "*default*";
124
124
  var TYPE_KEYS = /* @__PURE__ */ new Set(["typeAnnotation", "typeParameters", "typeArguments", "returnType", "superTypeParameters", "implements"]);
125
+ function readModule(read, file) {
126
+ try {
127
+ return moduleInfo(read(file), file);
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
125
132
  function createDepGraph({ root, read }) {
126
133
  const modules = /* @__PURE__ */ new Map();
127
134
  const resolutions = /* @__PURE__ */ new Map();
128
135
  const info = (file) => {
129
- if (!modules.has(file)) {
130
- let parsed = null;
131
- try {
132
- parsed = moduleInfo(read(file), file);
133
- } catch {
134
- parsed = null;
135
- }
136
- modules.set(file, parsed);
137
- }
136
+ if (!modules.has(file)) modules.set(file, readModule(read, file));
138
137
  return modules.get(file) ?? null;
139
138
  };
140
139
  return {
@@ -146,6 +145,7 @@ function createDepGraph({ root, read }) {
146
145
  },
147
146
  reset() {
148
147
  resolutions.clear();
148
+ for (const [file, parsed] of modules) if (parsed === null) modules.delete(file);
149
149
  },
150
150
  async build(entries, resolve6) {
151
151
  const resolveFrom = (spec, importer) => {
@@ -737,6 +737,7 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
737
737
  }
738
738
  }
739
739
  if (!metaNode && !program.body.some(isDefaultExport)) return [];
740
+ const assigned2 = assignedProps(program);
740
741
  const meta = objectOf(metaNode, bindings, wrappers);
741
742
  const title = stringOf(prop(meta, "title"), bindings) ?? autoTitle2;
742
743
  const include = storyFilter(meta, "includeStories");
@@ -747,7 +748,7 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
747
748
  const metaTags = tagsOf(meta);
748
749
  const native = wrappers.has(calleeOf(metaNode, bindings) ?? "");
749
750
  return exported.filter(({ name }) => name !== "__namedExportsOrder").filter(({ name }) => (!include || include(name)) && (!exclude || !exclude(name))).filter((e) => !native || (e.from ? foreign?.reexport(e.name) ?? true : mayBeStory(e.node, { bindings, wrappers, foreign }))).map(({ name, node, doc }) => {
750
- const spec = objectOf(node, bindings, wrappers);
751
+ const spec = withAssigned(objectOf(node, bindings, wrappers), assigned2.get(name));
751
752
  const isolate = isolateOf(spec, bindings, wrappers, name) ?? metaIsolate;
752
753
  const tags = combineTags(metaTags, tagsOf(spec));
753
754
  const viewport = native ? viewportOf(spec, bindings, wrappers) : void 0;
@@ -766,6 +767,23 @@ function indexProgram(program, file, autoTitle2, extra, foreign) {
766
767
  };
767
768
  });
768
769
  }
770
+ function assignedProps(program) {
771
+ const out = /* @__PURE__ */ new Map();
772
+ for (const stmt of program.body) {
773
+ const e = stmt.type === "ExpressionStatement" ? stmt.expression : void 0;
774
+ if (e?.type !== "AssignmentExpression" || e.operator !== "=" || e.left.type !== "MemberExpression") continue;
775
+ const { object, property, computed } = e.left;
776
+ if (object.type !== "Identifier" || computed || property.type !== "Identifier") continue;
777
+ const props = out.get(object.name) ?? [];
778
+ props.push({ type: "ObjectProperty", key: property, value: e.right, computed: false, shorthand: false });
779
+ out.set(object.name, props);
780
+ }
781
+ return out;
782
+ }
783
+ function withAssigned(spec, props) {
784
+ if (!props) return spec;
785
+ return { type: "ObjectExpression", properties: [...[...props].reverse(), ...spec?.properties ?? []] };
786
+ }
769
787
  function isolateOf(obj, bindings, wrappers, exportName) {
770
788
  const parameters = objectOf(prop(obj, "parameters"), bindings, wrappers);
771
789
  const forge2 = objectOf(prop(parameters, "forge"), bindings, wrappers);
@@ -1150,6 +1168,7 @@ async function createPropReader(root, stories, { skip } = {}) {
1150
1168
  } catch {
1151
1169
  return null;
1152
1170
  }
1171
+ if (typeof tsm.createLanguageService !== "function") return null;
1153
1172
  const { sys } = tsm;
1154
1173
  const configFile = tsm.findConfigFile(root, sys.fileExists);
1155
1174
  const read = configFile ? tsm.readConfigFile(configFile, sys.readFile) : void 0;
@@ -2189,6 +2208,23 @@ function watchSources(server, { files, depGraph, reindex, redeps, logError }) {
2189
2208
  });
2190
2209
  }
2191
2210
  }
2211
+ function coalesced(run, onError) {
2212
+ let running = false;
2213
+ let next = null;
2214
+ const start = (arg) => {
2215
+ running = true;
2216
+ run(arg).catch(onError).finally(() => {
2217
+ running = false;
2218
+ const queued = next;
2219
+ next = null;
2220
+ if (queued) start(queued.arg);
2221
+ });
2222
+ };
2223
+ return (arg) => {
2224
+ if (running) next = { arg };
2225
+ else start(arg);
2226
+ };
2227
+ }
2192
2228
  function companions(options, files) {
2193
2229
  const { storybookShims: shims, propControls: controls, api } = options;
2194
2230
  const storyFiles = () => [...files().keys()];
@@ -2211,7 +2247,6 @@ function forge(options) {
2211
2247
  let logger;
2212
2248
  let deps = null;
2213
2249
  let depsSent = null;
2214
- let depsPending = null;
2215
2250
  const wrappersOf = /* @__PURE__ */ new Map();
2216
2251
  let settledAll = null;
2217
2252
  const glob = () => {
@@ -2284,20 +2319,17 @@ function forge(options) {
2284
2319
  }
2285
2320
  }
2286
2321
  };
2287
- function redeps(server) {
2288
- if (depsSent === null || depsPending) return;
2289
- const resolve6 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
2290
- depsPending = Promise.resolve().then(async () => {
2291
- const graph = await graphOf(resolve6);
2292
- const json3 = JSON.stringify(graph);
2293
- if (json3 === depsSent) return;
2294
- depsSent = json3;
2295
- invalidate(server, ["deps.js"]);
2296
- server.ws.send({ type: "custom", event: "forge:deps", data: graph });
2297
- }).catch(logError).finally(() => {
2298
- depsPending = null;
2299
- });
2300
- }
2322
+ const rebuild = coalesced(async (server) => {
2323
+ const graph = await graphOf(async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null);
2324
+ const json3 = JSON.stringify(graph);
2325
+ if (json3 === depsSent) return;
2326
+ depsSent = json3;
2327
+ invalidate(server, ["deps.js"]);
2328
+ server.ws.send({ type: "custom", event: "forge:deps", data: graph });
2329
+ }, logError);
2330
+ const redeps = (server) => {
2331
+ if (depsSent !== null) rebuild(server);
2332
+ };
2301
2333
  const storiesReaching = (changed) => {
2302
2334
  const current = files();
2303
2335
  const seen = /* @__PURE__ */ new Set();