@weasel-js/forge 1.16.2 → 1.17.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.
- package/dist/{chunk-SFKDHXVE.js → chunk-MUBZSDOI.js} +4 -4
- package/dist/chunk-MUBZSDOI.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shell/index.js +1 -1
- package/dist/vite/index.js +56 -25
- package/dist/vite/index.js.map +1 -1
- package/package.json +14 -17
- package/dist/chunk-SFKDHXVE.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/forge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "A component workshop on labkit: stories in isolated frames, controls and state in lab trials.",
|
|
5
5
|
"weasel": {
|
|
6
6
|
"tier": "other"
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"url": "https://github.com/orochi235/weasel/issues"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
60
|
-
"node": "
|
|
60
|
+
"node": ">=24.11.0"
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
63
63
|
"dist",
|
|
@@ -72,27 +72,27 @@
|
|
|
72
72
|
"@testing-library/dom": "^10.4.0",
|
|
73
73
|
"@testing-library/user-event": "^14.6.1",
|
|
74
74
|
"@vitest/expect": "^5.0.2",
|
|
75
|
-
"@weasel-js/clock": "1.
|
|
76
|
-
"@weasel-js/icons": "1.
|
|
77
|
-
"@weasel-js/prefs": "1.
|
|
78
|
-
"@weasel-js/react": "1.
|
|
79
|
-
"@weasel-js/registry": "1.
|
|
80
|
-
"@weasel-js/svg": "1.
|
|
81
|
-
"@weasel-js/theme": "1.
|
|
82
|
-
"@weasel-js/ui": "1.
|
|
75
|
+
"@weasel-js/clock": "1.17.1",
|
|
76
|
+
"@weasel-js/icons": "1.17.1",
|
|
77
|
+
"@weasel-js/prefs": "1.17.1",
|
|
78
|
+
"@weasel-js/react": "1.17.1",
|
|
79
|
+
"@weasel-js/registry": "1.17.1",
|
|
80
|
+
"@weasel-js/svg": "1.17.1",
|
|
81
|
+
"@weasel-js/theme": "1.17.1",
|
|
82
|
+
"@weasel-js/ui": "1.17.1",
|
|
83
83
|
"axe-core": "^4.13.0",
|
|
84
|
-
"chai": "^6.2.2"
|
|
84
|
+
"chai": "^6.2.2",
|
|
85
|
+
"typescript": "~6.0.2"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
87
88
|
"@babel/types": "^8.0.6"
|
|
88
89
|
},
|
|
89
90
|
"peerDependencies": {
|
|
90
|
-
"@weasel-js/core": "1.
|
|
91
|
-
"@weasel-js/labkit": "1.
|
|
91
|
+
"@weasel-js/core": "1.17.1",
|
|
92
|
+
"@weasel-js/labkit": "1.17.1",
|
|
92
93
|
"react": "^19.0.0",
|
|
93
94
|
"playwright": "^1.50.0",
|
|
94
95
|
"react-dom": "^19.0.0",
|
|
95
|
-
"typescript": ">=5.0.0 <7",
|
|
96
96
|
"vite": "^8.0.0",
|
|
97
97
|
"vitest": "^4.0.0 || ^5.0.0"
|
|
98
98
|
},
|
|
@@ -100,9 +100,6 @@
|
|
|
100
100
|
"playwright": {
|
|
101
101
|
"optional": true
|
|
102
102
|
},
|
|
103
|
-
"typescript": {
|
|
104
|
-
"optional": true
|
|
105
|
-
},
|
|
106
103
|
"vitest": {
|
|
107
104
|
"optional": true
|
|
108
105
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/route/url.ts"],"names":[],"mappings":"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","file":"chunk-SFKDHXVE.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, 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) 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 || !isKnobParam(path)) 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"]}
|