@weasel-js/labkit 1.7.3 → 1.8.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.
Files changed (58) hide show
  1. package/dist/_dts/{CanvasStackContext-Cjy6I-Iv.d.ts → CanvasStackContext-C0fzFsQg.d.ts} +1 -1
  2. package/dist/_dts/{Stage-D6t7o0pF.d.ts → Stage-Cvwxdnfp.d.ts} +2 -2
  3. package/dist/_dts/{index-BTmaRy6a.d.ts → index-DEHRoTXK.d.ts} +2 -2
  4. package/dist/_dts/{index-DCMA4Fy1.d.ts → index-DRaswz91.d.ts} +1 -1
  5. package/dist/_dts/{types-BUBSIZuV.d.ts → types-DUfQ0isU.d.ts} +5 -1
  6. package/dist/_dts/{useTrialState-Bh8bzFgE.d.ts → useTrialState-CS4TdWPW.d.ts} +1 -1
  7. package/dist/canvas/index.d.ts +5 -5
  8. package/dist/chrome/index.d.ts +3 -3
  9. package/dist/chrome/index.js +4 -4
  10. package/dist/{chunk-4TJ34FXA.js → chunk-3L4MLLQB.js} +4 -4
  11. package/dist/chunk-3L4MLLQB.js.map +1 -0
  12. package/dist/{chunk-XSJJTXNA.js → chunk-7KAETQSS.js} +19 -19
  13. package/dist/chunk-7KAETQSS.js.map +1 -0
  14. package/dist/{chunk-YCFKW5RX.js → chunk-J5HJJDLF.js} +11 -2
  15. package/dist/chunk-J5HJJDLF.js.map +1 -0
  16. package/dist/{chunk-E6NARGU7.js → chunk-MOTDMMMN.js} +3 -3
  17. package/dist/{chunk-E6NARGU7.js.map → chunk-MOTDMMMN.js.map} +1 -1
  18. package/dist/{chunk-LPGBC63T.js → chunk-NVKBL7ZE.js} +3 -3
  19. package/dist/{chunk-LPGBC63T.js.map → chunk-NVKBL7ZE.js.map} +1 -1
  20. package/dist/chunk-WURZTJGQ.js +15756 -0
  21. package/dist/chunk-WURZTJGQ.js.map +1 -0
  22. package/dist/{chunk-IBVRMHW5.js → chunk-XXACAPVU.js} +3 -3
  23. package/dist/{chunk-IBVRMHW5.js.map → chunk-XXACAPVU.js.map} +1 -1
  24. package/dist/{chunk-4X44DMRS.js → chunk-YDE6XATS.js} +7 -7
  25. package/dist/{chunk-4X44DMRS.js.map → chunk-YDE6XATS.js.map} +1 -1
  26. package/dist/{chunk-HBGH344L.js → chunk-YX75W6EK.js} +53 -16
  27. package/dist/chunk-YX75W6EK.js.map +1 -0
  28. package/dist/config/index.d.ts +12 -5
  29. package/dist/config/index.js +2 -2
  30. package/dist/controls/index.d.ts +2 -2
  31. package/dist/controls/index.js +3 -3
  32. package/dist/dragdrop/index.d.ts +2 -2
  33. package/dist/index.d.ts +10 -10
  34. package/dist/index.js +175 -189
  35. package/dist/index.js.map +1 -1
  36. package/dist/layers/index.d.ts +2 -2
  37. package/dist/layers/index.js +1 -1
  38. package/dist/loupe/index.d.ts +1 -1
  39. package/dist/overview/index.d.ts +1 -1
  40. package/dist/passthrough/weasel-ui.js +1 -1
  41. package/dist/primitives/index.js +3 -3
  42. package/dist/state/index.d.ts +4 -4
  43. package/dist/state/index.js +3 -3
  44. package/dist/styles.css +9 -7
  45. package/dist/undo/index.d.ts +2 -2
  46. package/package.json +11 -11
  47. package/src/config/builder.ts +11 -1
  48. package/src/config/types.ts +5 -1
  49. package/src/controls/ControlMatrix.test.tsx +10 -0
  50. package/src/controls/ControlMatrix.tsx +58 -8
  51. package/src/controls/ControlPanel.test.tsx +30 -0
  52. package/src/theme/interstellar.theme.json +0 -14
  53. package/dist/chunk-4TJ34FXA.js.map +0 -1
  54. package/dist/chunk-HBGH344L.js.map +0 -1
  55. package/dist/chunk-IRMPZIYU.js +0 -15583
  56. package/dist/chunk-IRMPZIYU.js.map +0 -1
  57. package/dist/chunk-XSJJTXNA.js.map +0 -1
  58. package/dist/chunk-YCFKW5RX.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/sectionTree.ts","../src/config/useConfigSchema.ts"],"names":[],"mappings":"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","file":"chunk-E6NARGU7.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport { valueAtPath } from './path';\nimport type { ResolvedConfig } from './types';\nimport { isLeafVisible } from './visible';\n\n/**\n * A resolved schema rearranged so its sections are groups.\n *\n * `PrefsForm`'s rail navigates the schema tree — top-level groups are its\n * items, their group children indent under them. A resolved schema keeps its\n * sections beside the tree instead, so a flat schema's rail comes out as one\n * unnamed item however many headings the panel draws. This puts the sections\n * where the rail looks for them.\n */\nexport interface SectionTree {\n /** The rail's schema. Top-level children are the schema's root sections,\n * followed by any root node no section claimed. */\n group: PrefGroup;\n /** `config` renested to mirror `group` — the value tree `PrefsForm` takes. */\n values: Record<string, unknown>;\n /** The config path a path within `group` stands for. */\n pathAt: (railPath: string) => string;\n}\n\n/** Structural rather than `isPrefLeaf`, so the config entry stays free of a\n * runtime import from ui. */\nfunction isLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {\n return 'kind' in node;\n}\n\n/** A section label as an object key: no dots, since a dotted path is how every\n * caller addresses a node, and a label is free to hold one. */\nfunction slugOf(label: string, taken: ReadonlySet<string>): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '') || 'section';\n if (!taken.has(base)) return base;\n for (let n = 2; ; n += 1) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;\n}\n\n/** A subtree with everything `showIf` or `hidden` rules out cut away, or null\n * when that is all of it. A group whose children all went is not a rail item\n * the reader can open onto nothing. */\nfunction prune(\n resolved: ResolvedConfig,\n group: PrefGroup,\n at: string,\n config: Record<string, unknown>,\n showHidden: boolean,\n): PrefGroup | null {\n const children: PrefGroup['children'] = {};\n for (const [key, child] of Object.entries(group.children)) {\n const path = at === '' ? key : `${at}.${key}`;\n if (!isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n continue;\n }\n const kept = prune(resolved, child, path, config, showHidden);\n if (kept) children[key] = kept;\n }\n return Object.keys(children).length === 0 ? null : { ...group, children };\n}\n\n/**\n * Turn a resolved schema's root sections into the top level of a `PrefGroup`,\n * for `PrefsForm`'s rail layout.\n *\n * A section naming a group brings that group in whole, so it lands as an\n * indented rail item; a section naming a leaf drops it loose into the\n * section's own pane. Sections nested under a group are left alone — they\n * bucket rows inside a pane, which is the panel's job and not the rail's.\n */\nexport function sectionTree(\n resolved: ResolvedConfig,\n config: Record<string, unknown>,\n showHidden = false,\n): SectionTree {\n const children: PrefGroup['children'] = {};\n const values: Record<string, unknown> = {};\n const slugs = new Set<string>();\n const claimed = new Set<string>();\n\n for (const section of resolved.sections) {\n if (section.at !== '') continue;\n const kept: PrefGroup['children'] = {};\n const held: Record<string, unknown> = {};\n for (const path of section.paths) {\n claimed.add(path);\n const child = resolved.group.children[path];\n if (!child || !isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n kept[path] = child;\n } else {\n const subtree = prune(resolved, child, path, config, showHidden);\n if (!subtree) continue;\n kept[path] = subtree;\n }\n held[path] = valueAtPath(config, path);\n }\n if (Object.keys(kept).length === 0) continue;\n const slug = slugOf(section.label, slugs);\n slugs.add(slug);\n children[slug] = { name: section.label, children: kept };\n values[slug] = held;\n }\n\n // Whatever no section claimed stays at the top level, where `prefRailItems`\n // gathers loose leaves into one item named for the root.\n for (const [key, child] of Object.entries(resolved.group.children)) {\n if (claimed.has(key) || !isLeafVisible(resolved, key, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n } else {\n const subtree = prune(resolved, child, key, config, showHidden);\n if (!subtree) continue;\n children[key] = subtree;\n }\n values[key] = valueAtPath(config, key);\n }\n\n return {\n group: { name: resolved.group.name, children },\n values,\n pathAt: (railPath) => {\n const cut = railPath.indexOf('.');\n const head = cut === -1 ? railPath : railPath.slice(0, cut);\n if (!slugs.has(head)) return railPath;\n return cut === -1 ? '' : railPath.slice(cut + 1);\n },\n };\n}\n","import { useContext, useMemo } from 'react';\nimport type { Instrument } from '../instrument/types';\nimport { LabContext } from '../lab/LabContext';\nimport { fromConfigFields } from './fromConfigField';\nimport { resolveConfigSchema } from './resolve';\nimport type { ResolvedConfig } from './types';\n\nconst NO_RULES: readonly [] = [];\n\n/**\n * An instrument's controls, resolved against the surrounding lab's rules.\n *\n * Resolution cannot happen in `defineInstrument` — rules are lab-scoped and\n * that runs at module load — so it happens here, at render. Both instrument\n * paths land in the same shape, so callers never branch on which was used.\n */\nexport function useConfigSchema(\n // biome-ignore lint/suspicious/noExplicitAny: instruments are stored contravariantly; see InstrumentList\n instrument: Instrument<any, any, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n): ResolvedConfig {\n const lab = useContext(LabContext);\n const rules = lab?.configRules ?? NO_RULES;\n return useMemo(\n () =>\n instrument.config\n ? resolveConfigSchema(instrument.config, rules)\n : fromConfigFields(instrument.configSchema?.() ?? []),\n [instrument, rules],\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/config/sectionTree.ts","../src/config/useConfigSchema.ts"],"names":[],"mappings":"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","file":"chunk-MOTDMMMN.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport { valueAtPath } from './path';\nimport type { ResolvedConfig } from './types';\nimport { isLeafVisible } from './visible';\n\n/**\n * A resolved schema rearranged so its sections are groups.\n *\n * `PrefsForm`'s rail navigates the schema tree — top-level groups are its\n * items, their group children indent under them. A resolved schema keeps its\n * sections beside the tree instead, so a flat schema's rail comes out as one\n * unnamed item however many headings the panel draws. This puts the sections\n * where the rail looks for them.\n */\nexport interface SectionTree {\n /** The rail's schema. Top-level children are the schema's root sections,\n * followed by any root node no section claimed. */\n group: PrefGroup;\n /** `config` renested to mirror `group` — the value tree `PrefsForm` takes. */\n values: Record<string, unknown>;\n /** The config path a path within `group` stands for. */\n pathAt: (railPath: string) => string;\n}\n\n/** Structural rather than `isPrefLeaf`, so the config entry stays free of a\n * runtime import from ui. */\nfunction isLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {\n return 'kind' in node;\n}\n\n/** A section label as an object key: no dots, since a dotted path is how every\n * caller addresses a node, and a label is free to hold one. */\nfunction slugOf(label: string, taken: ReadonlySet<string>): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '') || 'section';\n if (!taken.has(base)) return base;\n for (let n = 2; ; n += 1) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;\n}\n\n/** A subtree with everything `showIf` or `hidden` rules out cut away, or null\n * when that is all of it. A group whose children all went is not a rail item\n * the reader can open onto nothing. */\nfunction prune(\n resolved: ResolvedConfig,\n group: PrefGroup,\n at: string,\n config: Record<string, unknown>,\n showHidden: boolean,\n): PrefGroup | null {\n const children: PrefGroup['children'] = {};\n for (const [key, child] of Object.entries(group.children)) {\n const path = at === '' ? key : `${at}.${key}`;\n if (!isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n continue;\n }\n const kept = prune(resolved, child, path, config, showHidden);\n if (kept) children[key] = kept;\n }\n return Object.keys(children).length === 0 ? null : { ...group, children };\n}\n\n/**\n * Turn a resolved schema's root sections into the top level of a `PrefGroup`,\n * for `PrefsForm`'s rail layout.\n *\n * A section naming a group brings that group in whole, so it lands as an\n * indented rail item; a section naming a leaf drops it loose into the\n * section's own pane. Sections nested under a group are left alone — they\n * bucket rows inside a pane, which is the panel's job and not the rail's.\n */\nexport function sectionTree(\n resolved: ResolvedConfig,\n config: Record<string, unknown>,\n showHidden = false,\n): SectionTree {\n const children: PrefGroup['children'] = {};\n const values: Record<string, unknown> = {};\n const slugs = new Set<string>();\n const claimed = new Set<string>();\n\n for (const section of resolved.sections) {\n if (section.at !== '') continue;\n const kept: PrefGroup['children'] = {};\n const held: Record<string, unknown> = {};\n for (const path of section.paths) {\n claimed.add(path);\n const child = resolved.group.children[path];\n if (!child || !isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n kept[path] = child;\n } else {\n const subtree = prune(resolved, child, path, config, showHidden);\n if (!subtree) continue;\n kept[path] = subtree;\n }\n held[path] = valueAtPath(config, path);\n }\n if (Object.keys(kept).length === 0) continue;\n const slug = slugOf(section.label, slugs);\n slugs.add(slug);\n children[slug] = { name: section.label, children: kept };\n values[slug] = held;\n }\n\n // Whatever no section claimed stays at the top level, where `prefRailItems`\n // gathers loose leaves into one item named for the root.\n for (const [key, child] of Object.entries(resolved.group.children)) {\n if (claimed.has(key) || !isLeafVisible(resolved, key, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n } else {\n const subtree = prune(resolved, child, key, config, showHidden);\n if (!subtree) continue;\n children[key] = subtree;\n }\n values[key] = valueAtPath(config, key);\n }\n\n return {\n group: { name: resolved.group.name, children },\n values,\n pathAt: (railPath) => {\n const cut = railPath.indexOf('.');\n const head = cut === -1 ? railPath : railPath.slice(0, cut);\n if (!slugs.has(head)) return railPath;\n return cut === -1 ? '' : railPath.slice(cut + 1);\n },\n };\n}\n","import { useContext, useMemo } from 'react';\nimport type { Instrument } from '../instrument/types';\nimport { LabContext } from '../lab/LabContext';\nimport { fromConfigFields } from './fromConfigField';\nimport { resolveConfigSchema } from './resolve';\nimport type { ResolvedConfig } from './types';\n\nconst NO_RULES: readonly [] = [];\n\n/**\n * An instrument's controls, resolved against the surrounding lab's rules.\n *\n * Resolution cannot happen in `defineInstrument` — rules are lab-scoped and\n * that runs at module load — so it happens here, at render. Both instrument\n * paths land in the same shape, so callers never branch on which was used.\n */\nexport function useConfigSchema(\n // biome-ignore lint/suspicious/noExplicitAny: instruments are stored contravariantly; see InstrumentList\n instrument: Instrument<any, any, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n): ResolvedConfig {\n const lab = useContext(LabContext);\n const rules = lab?.configRules ?? NO_RULES;\n return useMemo(\n () =>\n instrument.config\n ? resolveConfigSchema(instrument.config, rules)\n : fromConfigFields(instrument.configSchema?.() ?? []),\n [instrument, rules],\n );\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { je, Me, s, c2 } from './chunk-IRMPZIYU.js';
1
+ import { je, Me, s, c2 } from './chunk-WURZTJGQ.js';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useLatest } from '@weasel-js/core';
4
4
  import { useRef, useEffect, useMemo } from 'react';
@@ -205,5 +205,5 @@ function Split({
205
205
  }
206
206
 
207
207
  export { JobProgress, Legend, Readout, Split };
208
- //# sourceMappingURL=chunk-LPGBC63T.js.map
209
- //# sourceMappingURL=chunk-LPGBC63T.js.map
208
+ //# sourceMappingURL=chunk-NVKBL7ZE.js.map
209
+ //# sourceMappingURL=chunk-NVKBL7ZE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/primitives/JobProgress.tsx","../src/primitives/Legend.tsx","../src/primitives/Readout.tsx","../src/primitives/Split.tsx"],"names":["jsx","jsxs","c"],"mappings":"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","file":"chunk-LPGBC63T.js","sourcesContent":["import { ErrorIcon, WarningIcon } from '@weasel-js/ui';\nimport type { JobHandle } from '../job/types';\n\n/** Props for `<JobProgress>`. */\nexport interface JobProgressProps {\n job: JobHandle;\n className?: string;\n}\n\n/** A running job's progress, failures and outcome. Determinate once the job\n * reports a total; a job that cannot count up front never sends one, so the\n * bar stays indeterminate rather than inventing a denominator. */\nexport function JobProgress({ job, className }: JobProgressProps) {\n const { status, done, total, failures, error } = job;\n if (status === 'idle') return null;\n\n const determinate = total !== null && total > 0;\n const fraction = determinate ? Math.min(1, done / total) : 0;\n const cls = ['lk-job', `lk-job--${status}`, className].filter(Boolean).join(' ');\n\n return (\n <div className={cls}>\n <div\n className={`lk-job__track${determinate ? '' : ' lk-job__track--indeterminate'}`}\n role=\"progressbar\"\n aria-label=\"Job progress\"\n aria-valuenow={determinate ? done : undefined}\n aria-valuemin={determinate ? 0 : undefined}\n aria-valuemax={determinate ? total : undefined}\n >\n {/* Scaling a full-width bar keeps the fill off the layout path, so\n progress updates never reflow the status bar. */}\n <div\n className=\"lk-job__fill\"\n style={{ transform: `scaleX(${determinate ? fraction : 1})` }}\n />\n </div>\n\n <span className=\"lk-job__count\">\n {done}\n {determinate ? ` / ${total}` : ''}\n </span>\n\n {failures.length > 0 && (\n <span className=\"lk-job__failures\" title={failures.map((f) => f.error).join('\\n')}>\n <WarningIcon size={14} />\n {failures.length} failed\n </span>\n )}\n\n {error && (\n <span className=\"lk-job__error\" title={error}>\n <ErrorIcon size={14} />\n {error}\n </span>\n )}\n\n {status === 'running' && (\n <button type=\"button\" className=\"lk-job__cancel\" onClick={job.cancel}>\n Cancel\n </button>\n )}\n </div>\n );\n}\n","import type { CSSProperties } from 'react';\n\n/** How a legend entry's swatch is drawn, matching how the thing looks on canvas. */\nexport type LegendMark = 'line' | 'dash' | 'dot' | 'band';\n\n/** One row of a legend: a swatch and what it means. */\nexport interface LegendEntry {\n key: string;\n label: string;\n color: string;\n /** Defaults to `'line'`. */\n mark?: LegendMark;\n}\n\n/** Props for `<Legend>`. */\nexport interface LegendProps {\n entries: readonly LegendEntry[];\n className?: string;\n}\n\n/** A color key. Presentational only — no handlers, no state, no hover behavior. */\nexport function Legend({ entries, className }: LegendProps) {\n return (\n <ul className={className ? `lk-legend ${className}` : 'lk-legend'}>\n {entries.map((entry) => {\n const mark = entry.mark ?? 'line';\n return (\n <li className=\"lk-legend__row\" key={entry.key}>\n <span\n aria-hidden=\"true\"\n className={`lk-legend__swatch lk-legend__swatch--${mark}`}\n style={{ '--lk-legend-ink': entry.color } as CSSProperties}\n />\n <span className=\"lk-legend__label\">{entry.label}</span>\n </li>\n );\n })}\n </ul>\n );\n}\n","import { DetailList, type DetailListValues, DetailRow, type DetailRowStatus } from '@weasel-js/ui';\nimport type { ReactNode } from 'react';\n\n/** One measurement in a {@link Readout}. */\nexport interface ReadoutRow {\n /** React key. Defaults to `label` when that is a string, else the row's index. */\n key?: string;\n label: ReactNode;\n /** `null` or `undefined` keeps the row in place showing an en dash, so the\n * readout holds its height while a measurement comes and goes. */\n value?: ReactNode;\n /** A dot in the status color before the value. Color is never the only\n * signal: the value has to say the same thing in words. */\n status?: DetailRowStatus;\n /** `text` sets this row's value as wrapping prose instead of a figure. */\n values?: DetailListValues;\n}\n\n/** Props for {@link Readout}. */\nexport interface ReadoutProps {\n rows: readonly ReadoutRow[];\n /** Heading above the rows, which also names the list. */\n title?: ReactNode;\n /** Drawn below the rows — a small table, a note. */\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Values a lab only reads — measurements shown beside its picture, such as\n * \"Lock margin +47.9°\". A {@link DetailList} set as figures: labels take the\n * params label recipe and `--wzl-params-label-width` rail, so a readout lines\n * up with a `ControlPanel` beside it, and values sit right-aligned in the mono\n * face with spaces kept, so figure-space padding aligns a column on its\n * decimal point. Rows compose by hand as `DetailRow`s inside a `DetailList`\n * with `values=\"figures\"`.\n */\nexport function Readout({ rows, title, children, className }: ReadoutProps) {\n return (\n <div className={className ? `lk-readout ${className}` : 'lk-readout'}>\n <DetailList title={title} values=\"figures\">\n {rows.map((row, i) => (\n <DetailRow\n key={row.key ?? (typeof row.label === 'string' ? row.label : i)}\n label={row.label}\n status={row.status}\n values={row.values}\n >\n {row.value}\n </DetailRow>\n ))}\n </DetailList>\n {children != null && <div className=\"lk-readout__footer\">{children}</div>}\n </div>\n );\n}\n","import { useLatest } from '@weasel-js/core';\nimport { type ReactNode, useEffect, useMemo, useRef } from 'react';\nimport { asNodeId, createNode, Store, stripStrategy } from 'windease';\nimport { type ChromeMap, Container, Provider, StrategyRegistryProvider } from 'windease/react';\n\nconst ZONE_ID = asNodeId('lk-split');\nconst SIDEBAR_ID = asNodeId('lk-split-sidebar');\nconst CONTENT_ID = asNodeId('lk-split-content');\nconst STRATEGIES = { strip: stripStrategy as never };\n\n/** Props for `<Split>`. */\nexport interface SplitProps {\n /** The sidebar pane's contents. */\n sidebar: ReactNode;\n /** The edge the sidebar sits on. Read once, when the strip mounts. Default `'start'`. */\n side?: 'start' | 'end';\n /** The content pane's contents. */\n children: ReactNode;\n /** Names the sidebar pane, and through it the seam. Default `'Sidebar'`. */\n label?: string;\n /** Extra classes on the strip, which always has `lk-split`. */\n className?: string;\n /** The class on the sidebar pane. */\n sidebarClassName?: string;\n /** The class on the content pane. */\n contentClassName?: string;\n /** The sidebar's current extent in pixels. Omit to leave it unpersisted: the\n * seam still works, its result just does not outlive the mount. */\n width?: number;\n /** Where the sidebar starts before anyone drags it. */\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /** How little the content pane may be squeezed to. Reported as the seam's\n * reachable maximum, so the drag stops there rather than clamping silently. */\n contentMinWidth?: number;\n /** Fires through the drag, not on release: the pane is already there. */\n onWidthChange?: (width: number) => void;\n /**\n * Fixed extent. Omit in an app — the strip measures its own box. Supply it\n * where nothing measures, notably jsdom, or both panes lay out at zero.\n */\n viewport?: { w: number; h: number };\n}\n\ninterface SplitInit {\n side: 'start' | 'end';\n label: string;\n sidebarWidth: number;\n minWidth: number;\n maxWidth: number;\n contentMinWidth: number;\n}\n\n/** A strip holding the sidebar and the content pane, sized as the first render asks. */\nfunction createSplitStore({\n side,\n label,\n sidebarWidth,\n minWidth,\n maxWidth,\n contentMinWidth,\n}: SplitInit): Store {\n const store = new Store();\n store.registerNode(\n createNode({\n kind: 'zone',\n id: ZONE_ID,\n // A strip's seam resizes the pane before it, which on the end side is the\n // content; 'neighbor' has the seam size the sidebar after it too.\n container: {\n strategyId: 'strip',\n config: {\n axis: 'x',\n resizable: true,\n ...(side === 'end' ? { resizeMode: 'neighbor' } : {}),\n },\n },\n }),\n );\n const sidebarNode = createNode({\n kind: 'sidebar',\n id: SIDEBAR_ID,\n parentId: ZONE_ID,\n meta: { title: label },\n placement: { size: { w: sidebarWidth } },\n hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } },\n });\n const contentNode = createNode({\n kind: 'content',\n id: CONTENT_ID,\n parentId: ZONE_ID,\n meta: { title: 'Content' },\n hints: { minSize: { w: contentMinWidth, h: 0 } },\n });\n // A strip lays its children out in registration order.\n for (const node of side === 'end' ? [contentNode, sidebarNode] : [sidebarNode, contentNode]) {\n store.registerNode(node);\n }\n store.showNode(SIDEBAR_ID);\n store.showNode(CONTENT_ID);\n return store;\n}\n\n/**\n * A sidebar and a content pane as a two-pane strip with a draggable seam.\n *\n * The seam is `stripStrategy`'s own resize affordance, so it arrives as a\n * `role=\"separator\"` carrying the reachable range, operable by pointer and by\n * arrows / Home / End, and clamped against the content pane's floor rather\n * than against the sidebar's alone.\n */\nexport function Split({\n sidebar,\n children,\n side = 'start',\n label = 'Sidebar',\n className,\n sidebarClassName,\n contentClassName,\n width,\n defaultWidth = 320,\n minWidth = 140,\n maxWidth = 720,\n contentMinWidth = 160,\n onWidthChange,\n viewport,\n}: SplitProps) {\n // The cross-axis half of every size hint below is inert: a horizontal strip\n // stretches its panes to the container's height and never reads `h`.\n const storeRef = useRef<Store | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createSplitStore({\n side,\n label,\n sidebarWidth: width ?? defaultWidth,\n minWidth,\n maxWidth,\n contentMinWidth,\n });\n }\n const store = storeRef.current;\n\n // What the store last held, so the placement event can tell a drag from the\n // echo of our own write and a controlled `width` does not fight the seam.\n const widthRef = useRef(width ?? defaultWidth);\n\n useEffect(() => {\n store.setHints(SIDEBAR_ID, {\n minSize: { w: minWidth, h: 0 },\n maxSize: { w: maxWidth, h: 0 },\n });\n store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });\n }, [store, minWidth, maxWidth, contentMinWidth]);\n\n useEffect(() => {\n store.setMeta(SIDEBAR_ID, { title: label });\n }, [store, label]);\n\n useEffect(() => {\n if (width === undefined || width === widthRef.current) return;\n widthRef.current = width;\n store.patchPlacement(SIDEBAR_ID, { size: { w: width } });\n }, [store, width]);\n\n const onWidthChangeRef = useLatest(onWidthChange);\n useEffect(\n () =>\n store.events.on('node.placementChanged', () => {\n // A neighbor drag pins the content's width too; unpinned, it goes on filling the strip.\n if ((store.getPlacement(CONTENT_ID)?.size as { w?: number } | undefined)?.w !== undefined) {\n store.patchPlacement(CONTENT_ID, { size: {} });\n }\n const next = (store.getPlacement(SIDEBAR_ID)?.size as { w?: number } | undefined)?.w;\n if (typeof next !== 'number' || next === widthRef.current) return;\n widthRef.current = next;\n onWidthChangeRef.current?.(next);\n }),\n [onWidthChangeRef, store],\n );\n\n const chrome = useMemo<ChromeMap>(\n () => ({\n sidebar: () => <div className={sidebarClassName}>{sidebar}</div>,\n content: () => <div className={contentClassName}>{children}</div>,\n }),\n [sidebar, children, sidebarClassName, contentClassName],\n );\n\n return (\n <Provider store={store}>\n <StrategyRegistryProvider strategies={STRATEGIES}>\n <Container\n parentId={ZONE_ID}\n chrome={chrome}\n className={className ? `lk-split windease-zone ${className}` : 'lk-split windease-zone'}\n affordances\n viewport={viewport}\n />\n </StrategyRegistryProvider>\n </Provider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/primitives/JobProgress.tsx","../src/primitives/Legend.tsx","../src/primitives/Readout.tsx","../src/primitives/Split.tsx"],"names":["jsx","jsxs","c"],"mappings":"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","file":"chunk-NVKBL7ZE.js","sourcesContent":["import { ErrorIcon, WarningIcon } from '@weasel-js/ui';\nimport type { JobHandle } from '../job/types';\n\n/** Props for `<JobProgress>`. */\nexport interface JobProgressProps {\n job: JobHandle;\n className?: string;\n}\n\n/** A running job's progress, failures and outcome. Determinate once the job\n * reports a total; a job that cannot count up front never sends one, so the\n * bar stays indeterminate rather than inventing a denominator. */\nexport function JobProgress({ job, className }: JobProgressProps) {\n const { status, done, total, failures, error } = job;\n if (status === 'idle') return null;\n\n const determinate = total !== null && total > 0;\n const fraction = determinate ? Math.min(1, done / total) : 0;\n const cls = ['lk-job', `lk-job--${status}`, className].filter(Boolean).join(' ');\n\n return (\n <div className={cls}>\n <div\n className={`lk-job__track${determinate ? '' : ' lk-job__track--indeterminate'}`}\n role=\"progressbar\"\n aria-label=\"Job progress\"\n aria-valuenow={determinate ? done : undefined}\n aria-valuemin={determinate ? 0 : undefined}\n aria-valuemax={determinate ? total : undefined}\n >\n {/* Scaling a full-width bar keeps the fill off the layout path, so\n progress updates never reflow the status bar. */}\n <div\n className=\"lk-job__fill\"\n style={{ transform: `scaleX(${determinate ? fraction : 1})` }}\n />\n </div>\n\n <span className=\"lk-job__count\">\n {done}\n {determinate ? ` / ${total}` : ''}\n </span>\n\n {failures.length > 0 && (\n <span className=\"lk-job__failures\" title={failures.map((f) => f.error).join('\\n')}>\n <WarningIcon size={14} />\n {failures.length} failed\n </span>\n )}\n\n {error && (\n <span className=\"lk-job__error\" title={error}>\n <ErrorIcon size={14} />\n {error}\n </span>\n )}\n\n {status === 'running' && (\n <button type=\"button\" className=\"lk-job__cancel\" onClick={job.cancel}>\n Cancel\n </button>\n )}\n </div>\n );\n}\n","import type { CSSProperties } from 'react';\n\n/** How a legend entry's swatch is drawn, matching how the thing looks on canvas. */\nexport type LegendMark = 'line' | 'dash' | 'dot' | 'band';\n\n/** One row of a legend: a swatch and what it means. */\nexport interface LegendEntry {\n key: string;\n label: string;\n color: string;\n /** Defaults to `'line'`. */\n mark?: LegendMark;\n}\n\n/** Props for `<Legend>`. */\nexport interface LegendProps {\n entries: readonly LegendEntry[];\n className?: string;\n}\n\n/** A color key. Presentational only — no handlers, no state, no hover behavior. */\nexport function Legend({ entries, className }: LegendProps) {\n return (\n <ul className={className ? `lk-legend ${className}` : 'lk-legend'}>\n {entries.map((entry) => {\n const mark = entry.mark ?? 'line';\n return (\n <li className=\"lk-legend__row\" key={entry.key}>\n <span\n aria-hidden=\"true\"\n className={`lk-legend__swatch lk-legend__swatch--${mark}`}\n style={{ '--lk-legend-ink': entry.color } as CSSProperties}\n />\n <span className=\"lk-legend__label\">{entry.label}</span>\n </li>\n );\n })}\n </ul>\n );\n}\n","import { DetailList, type DetailListValues, DetailRow, type DetailRowStatus } from '@weasel-js/ui';\nimport type { ReactNode } from 'react';\n\n/** One measurement in a {@link Readout}. */\nexport interface ReadoutRow {\n /** React key. Defaults to `label` when that is a string, else the row's index. */\n key?: string;\n label: ReactNode;\n /** `null` or `undefined` keeps the row in place showing an en dash, so the\n * readout holds its height while a measurement comes and goes. */\n value?: ReactNode;\n /** A dot in the status color before the value. Color is never the only\n * signal: the value has to say the same thing in words. */\n status?: DetailRowStatus;\n /** `text` sets this row's value as wrapping prose instead of a figure. */\n values?: DetailListValues;\n}\n\n/** Props for {@link Readout}. */\nexport interface ReadoutProps {\n rows: readonly ReadoutRow[];\n /** Heading above the rows, which also names the list. */\n title?: ReactNode;\n /** Drawn below the rows — a small table, a note. */\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Values a lab only reads — measurements shown beside its picture, such as\n * \"Lock margin +47.9°\". A {@link DetailList} set as figures: labels take the\n * params label recipe and `--wzl-params-label-width` rail, so a readout lines\n * up with a `ControlPanel` beside it, and values sit right-aligned in the mono\n * face with spaces kept, so figure-space padding aligns a column on its\n * decimal point. Rows compose by hand as `DetailRow`s inside a `DetailList`\n * with `values=\"figures\"`.\n */\nexport function Readout({ rows, title, children, className }: ReadoutProps) {\n return (\n <div className={className ? `lk-readout ${className}` : 'lk-readout'}>\n <DetailList title={title} values=\"figures\">\n {rows.map((row, i) => (\n <DetailRow\n key={row.key ?? (typeof row.label === 'string' ? row.label : i)}\n label={row.label}\n status={row.status}\n values={row.values}\n >\n {row.value}\n </DetailRow>\n ))}\n </DetailList>\n {children != null && <div className=\"lk-readout__footer\">{children}</div>}\n </div>\n );\n}\n","import { useLatest } from '@weasel-js/core';\nimport { type ReactNode, useEffect, useMemo, useRef } from 'react';\nimport { asNodeId, createNode, Store, stripStrategy } from 'windease';\nimport { type ChromeMap, Container, Provider, StrategyRegistryProvider } from 'windease/react';\n\nconst ZONE_ID = asNodeId('lk-split');\nconst SIDEBAR_ID = asNodeId('lk-split-sidebar');\nconst CONTENT_ID = asNodeId('lk-split-content');\nconst STRATEGIES = { strip: stripStrategy as never };\n\n/** Props for `<Split>`. */\nexport interface SplitProps {\n /** The sidebar pane's contents. */\n sidebar: ReactNode;\n /** The edge the sidebar sits on. Read once, when the strip mounts. Default `'start'`. */\n side?: 'start' | 'end';\n /** The content pane's contents. */\n children: ReactNode;\n /** Names the sidebar pane, and through it the seam. Default `'Sidebar'`. */\n label?: string;\n /** Extra classes on the strip, which always has `lk-split`. */\n className?: string;\n /** The class on the sidebar pane. */\n sidebarClassName?: string;\n /** The class on the content pane. */\n contentClassName?: string;\n /** The sidebar's current extent in pixels. Omit to leave it unpersisted: the\n * seam still works, its result just does not outlive the mount. */\n width?: number;\n /** Where the sidebar starts before anyone drags it. */\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /** How little the content pane may be squeezed to. Reported as the seam's\n * reachable maximum, so the drag stops there rather than clamping silently. */\n contentMinWidth?: number;\n /** Fires through the drag, not on release: the pane is already there. */\n onWidthChange?: (width: number) => void;\n /**\n * Fixed extent. Omit in an app — the strip measures its own box. Supply it\n * where nothing measures, notably jsdom, or both panes lay out at zero.\n */\n viewport?: { w: number; h: number };\n}\n\ninterface SplitInit {\n side: 'start' | 'end';\n label: string;\n sidebarWidth: number;\n minWidth: number;\n maxWidth: number;\n contentMinWidth: number;\n}\n\n/** A strip holding the sidebar and the content pane, sized as the first render asks. */\nfunction createSplitStore({\n side,\n label,\n sidebarWidth,\n minWidth,\n maxWidth,\n contentMinWidth,\n}: SplitInit): Store {\n const store = new Store();\n store.registerNode(\n createNode({\n kind: 'zone',\n id: ZONE_ID,\n // A strip's seam resizes the pane before it, which on the end side is the\n // content; 'neighbor' has the seam size the sidebar after it too.\n container: {\n strategyId: 'strip',\n config: {\n axis: 'x',\n resizable: true,\n ...(side === 'end' ? { resizeMode: 'neighbor' } : {}),\n },\n },\n }),\n );\n const sidebarNode = createNode({\n kind: 'sidebar',\n id: SIDEBAR_ID,\n parentId: ZONE_ID,\n meta: { title: label },\n placement: { size: { w: sidebarWidth } },\n hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } },\n });\n const contentNode = createNode({\n kind: 'content',\n id: CONTENT_ID,\n parentId: ZONE_ID,\n meta: { title: 'Content' },\n hints: { minSize: { w: contentMinWidth, h: 0 } },\n });\n // A strip lays its children out in registration order.\n for (const node of side === 'end' ? [contentNode, sidebarNode] : [sidebarNode, contentNode]) {\n store.registerNode(node);\n }\n store.showNode(SIDEBAR_ID);\n store.showNode(CONTENT_ID);\n return store;\n}\n\n/**\n * A sidebar and a content pane as a two-pane strip with a draggable seam.\n *\n * The seam is `stripStrategy`'s own resize affordance, so it arrives as a\n * `role=\"separator\"` carrying the reachable range, operable by pointer and by\n * arrows / Home / End, and clamped against the content pane's floor rather\n * than against the sidebar's alone.\n */\nexport function Split({\n sidebar,\n children,\n side = 'start',\n label = 'Sidebar',\n className,\n sidebarClassName,\n contentClassName,\n width,\n defaultWidth = 320,\n minWidth = 140,\n maxWidth = 720,\n contentMinWidth = 160,\n onWidthChange,\n viewport,\n}: SplitProps) {\n // The cross-axis half of every size hint below is inert: a horizontal strip\n // stretches its panes to the container's height and never reads `h`.\n const storeRef = useRef<Store | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createSplitStore({\n side,\n label,\n sidebarWidth: width ?? defaultWidth,\n minWidth,\n maxWidth,\n contentMinWidth,\n });\n }\n const store = storeRef.current;\n\n // What the store last held, so the placement event can tell a drag from the\n // echo of our own write and a controlled `width` does not fight the seam.\n const widthRef = useRef(width ?? defaultWidth);\n\n useEffect(() => {\n store.setHints(SIDEBAR_ID, {\n minSize: { w: minWidth, h: 0 },\n maxSize: { w: maxWidth, h: 0 },\n });\n store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });\n }, [store, minWidth, maxWidth, contentMinWidth]);\n\n useEffect(() => {\n store.setMeta(SIDEBAR_ID, { title: label });\n }, [store, label]);\n\n useEffect(() => {\n if (width === undefined || width === widthRef.current) return;\n widthRef.current = width;\n store.patchPlacement(SIDEBAR_ID, { size: { w: width } });\n }, [store, width]);\n\n const onWidthChangeRef = useLatest(onWidthChange);\n useEffect(\n () =>\n store.events.on('node.placementChanged', () => {\n // A neighbor drag pins the content's width too; unpinned, it goes on filling the strip.\n if ((store.getPlacement(CONTENT_ID)?.size as { w?: number } | undefined)?.w !== undefined) {\n store.patchPlacement(CONTENT_ID, { size: {} });\n }\n const next = (store.getPlacement(SIDEBAR_ID)?.size as { w?: number } | undefined)?.w;\n if (typeof next !== 'number' || next === widthRef.current) return;\n widthRef.current = next;\n onWidthChangeRef.current?.(next);\n }),\n [onWidthChangeRef, store],\n );\n\n const chrome = useMemo<ChromeMap>(\n () => ({\n sidebar: () => <div className={sidebarClassName}>{sidebar}</div>,\n content: () => <div className={contentClassName}>{children}</div>,\n }),\n [sidebar, children, sidebarClassName, contentClassName],\n );\n\n return (\n <Provider store={store}>\n <StrategyRegistryProvider strategies={STRATEGIES}>\n <Container\n parentId={ZONE_ID}\n chrome={chrome}\n className={className ? `lk-split windease-zone ${className}` : 'lk-split windease-zone'}\n affordances\n viewport={viewport}\n />\n </StrategyRegistryProvider>\n </Provider>\n );\n}\n"]}