@things-factory/figure-ui 10.1.208 → 10.1.211

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 (92) hide show
  1. package/client/modeller/data-interface.ts +5 -5
  2. package/client/modeller/editor.ts +97 -88
  3. package/client/modeller/fasten-geometry.ts +2 -1
  4. package/client/modeller/figure-links.ts +87 -157
  5. package/client/modeller/figure-size-editor.ts +233 -0
  6. package/client/modeller/joint-drag.ts +25 -21
  7. package/client/modeller/joint-fan.ts +4 -3
  8. package/client/modeller/links.ts +87 -36
  9. package/client/modeller/live.ts +33 -0
  10. package/client/modeller/motion-ends.ts +10 -6
  11. package/client/modeller/size-response.ts +95 -0
  12. package/dist-client/modeller/data-interface.d.ts +1 -1
  13. package/dist-client/modeller/data-interface.js +5 -5
  14. package/dist-client/modeller/data-interface.js.map +1 -1
  15. package/dist-client/modeller/editor.d.ts +14 -1
  16. package/dist-client/modeller/editor.js +103 -89
  17. package/dist-client/modeller/editor.js.map +1 -1
  18. package/dist-client/modeller/fasten-geometry.js +1 -1
  19. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  20. package/dist-client/modeller/figure-links.d.ts +3 -4
  21. package/dist-client/modeller/figure-links.js +91 -145
  22. package/dist-client/modeller/figure-links.js.map +1 -1
  23. package/dist-client/modeller/figure-size-editor.d.ts +29 -0
  24. package/dist-client/modeller/figure-size-editor.js +262 -0
  25. package/dist-client/modeller/figure-size-editor.js.map +1 -0
  26. package/dist-client/modeller/joint-drag.d.ts +1 -1
  27. package/dist-client/modeller/joint-drag.js +27 -22
  28. package/dist-client/modeller/joint-drag.js.map +1 -1
  29. package/dist-client/modeller/joint-fan.js +4 -3
  30. package/dist-client/modeller/joint-fan.js.map +1 -1
  31. package/dist-client/modeller/links.d.ts +21 -9
  32. package/dist-client/modeller/links.js +85 -35
  33. package/dist-client/modeller/links.js.map +1 -1
  34. package/dist-client/modeller/live.d.ts +14 -0
  35. package/dist-client/modeller/live.js +31 -0
  36. package/dist-client/modeller/live.js.map +1 -1
  37. package/dist-client/modeller/motion-ends.d.ts +4 -1
  38. package/dist-client/modeller/motion-ends.js +10 -6
  39. package/dist-client/modeller/motion-ends.js.map +1 -1
  40. package/dist-client/modeller/size-response.d.ts +29 -0
  41. package/dist-client/modeller/size-response.js +99 -0
  42. package/dist-client/modeller/size-response.js.map +1 -0
  43. package/dist-client/tsconfig.tsbuildinfo +1 -1
  44. package/docs/evidence/2026-10-24/current-size-panel.png +0 -0
  45. package/docs/evidence/2026-10-24/integrated-size.png +0 -0
  46. package/docs/evidence/2026-10-24/multipart-response.png +0 -0
  47. package/docs/evidence/2026-10-24/repeat-layout.png +0 -0
  48. package/docs/evidence/2026-10-24/repeat-shared-style.png +0 -0
  49. package/docs/evidence/2026-10-24/size-in-layout.png +0 -0
  50. package/docs/evidence/2026-10-24/size-shared-style.png +0 -0
  51. package/docs/evidence/2026-10-24/width-preview.png +0 -0
  52. package/docs/multipart-response-review-2026-10-24.md +77 -0
  53. package/docs/response-authoring-2026-10-24.md +46 -0
  54. package/docs/size-and-repeat-ux-2026-10-24.md +67 -0
  55. package/package.json +7 -7
  56. package/test/attachment-size-contract.test.ts +47 -0
  57. package/test/browser/acceptance-page.js +1 -1
  58. package/test/browser/detail-page.js +1 -1
  59. package/test/browser/detail-tabs-page.js +1 -1
  60. package/test/browser/editing-page.js +1 -1
  61. package/test/browser/editing.check.mjs +66 -14
  62. package/test/browser/links.check.mjs +41 -22
  63. package/test/browser/modeller-page.js +1 -1
  64. package/test/browser/modeller-revision.check.mjs +21 -7
  65. package/test/browser/modeller.check.mjs +106 -39
  66. package/test/browser/multipart-response-page.js +8 -0
  67. package/test/browser/parts.check.mjs +2 -1
  68. package/test/browser/product-page.js +1 -1
  69. package/test/browser/props.check.mjs +17 -14
  70. package/test/browser/response-page.js +12 -0
  71. package/test/browser/row-preview.check.mjs +3 -3
  72. package/test/browser/screen.check.mjs +57 -13
  73. package/test/browser/serve-response.mjs +24 -0
  74. package/test/clip-ai-proposal.test.ts +1 -1
  75. package/test/clip-consent.test.ts +2 -2
  76. package/test/command-preview.test.ts +7 -7
  77. package/test/joint-drag.test.ts +6 -6
  78. package/test/joint-frames.test.ts +33 -32
  79. package/test/joints.ts +1 -6
  80. package/test/links.test.ts +60 -20
  81. package/test/multipart-response-fixture.js +17 -0
  82. package/test/multipart-response.test.ts +37 -0
  83. package/test/refusals.test.ts +2 -2
  84. package/test/resize-with-placement.test.ts +23 -1
  85. package/test/size-response.test.ts +49 -0
  86. package/test/size-source-pairing.test.ts +66 -0
  87. package/translations/en.json +47 -10
  88. package/translations/es.json +47 -10
  89. package/translations/ja.json +47 -10
  90. package/translations/ko.json +52 -15
  91. package/translations/ms.json +47 -10
  92. package/translations/zh.json +47 -10
@@ -1 +1 @@
1
- {"version":3,"file":"motion-ends.js","sourceRoot":"","sources":["../../client/modeller/motion-ends.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileFigureAsset } from '@hatiolab/figure-model'\n\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { boxShape } from './drawn-shapes.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * Where a part's motion takes it (ADR-0097 §5.5, designer 2026-09-27): the bodies its control moves, drawn translucent at\n * the control's lowest and highest value -- evaluated there by figure-model, not guessed -- and, for a slide, the line it\n * travels. Only the two ends are shown; nothing here says what lies between them is clear.\n */\n\ntype Pose = { r: number[][]; t: number[] }\ntype Body = FigureRenderPart & { pose: Pose }\n\n/** The line a slide travels along, thin enough not to hide the part. */\nconst THICKNESS = 6\n\nexport interface MotionEnds {\n control: string\n min: number\n max: number\n unit: string\n /** The moving bodies at the lowest value, then at the highest. */\n ghost: FigureRenderPart[]\n}\n\nconst samePose = (a: Pose, b: Pose) => [0, 1, 2].every(i => Math.abs(a.t[i]! - b.t[i]!) < 1e-6 && [0, 1, 2].every(k => Math.abs(a.r[i]![k]! - b.r[i]![k]!) < 1e-9))\n\n/** The ends of the selected part's own motion, or nothing: a part that does not move, follows another, or turns without end. */\nexport function motionEndsOf(revision: FigureRevision, part: string): MotionEnds | null {\n const asset = revision.asset\n const joint = asset.joints?.find(j => j.body1.part === part)\n /* A turn without end is a revolute joint whose control has no range: the range check below leaves it out. */\n if (!joint || joint.type === 'fixed' || joint.mimic) return null\n const control = joint.state?.id ?? joint.id\n const input = asset.document.model.inputs.find(i => i.id === control && i.role === 'state')\n if (!input || typeof input.min !== 'number' || typeof input.max !== 'number' || input.min === input.max) return null\n /* The version's design values are its asset's; its pose is every control as set now, this one at each end. */\n const state = Object.fromEntries(Object.entries(revision.inputs).filter(([id]) => id in asset.stateDefaults))\n const at = (value: number) => {\n try {\n return compileFigureAsset(asset).evaluate({ ...state, [control]: value }).geometry as unknown as Body[]\n } catch {\n return null\n }\n }\n const low = at(input.min)\n const high = at(input.max)\n if (!low || !high) return null\n const byId = new Map(high.filter(b => b.pose).map(b => [b.id, b]))\n const moving = low.filter(b => b.pose && 'shape' in b && byId.has(b.id) && !samePose(b.pose, byId.get(b.id)!.pose))\n if (!moving.length) return null\n const ghost: FigureRenderPart[] = [\n ...moving.map(b => ({ ...b, id: `${PREVIEW}:min:${b.id}`, material: { token: PREVIEW, transparent: true } })),\n ...moving.map(b => ({ ...byId.get(b.id)!, id: `${PREVIEW}:max:${b.id}`, material: { token: PREVIEW, transparent: true } }))\n ]\n /* A slide: the line its own body travels, from the lowest value to the highest. */\n const own = moving.find(b => b.id === part)\n if (joint.type === 'prismatic' && own) {\n const from = own.pose.t\n const to = byId.get(part)!.pose.t\n const v = [0, 1, 2].map(i => to[i]! - from[i]!)\n const length = Math.hypot(...v)\n if (length > 1e-6) {\n const x = v.map(c => c / length)\n /* Any two directions square to it and to each other complete its frame. */\n const helper = Math.abs(x[1]!) < 0.9 ? [0, 1, 0] : [1, 0, 0]\n const zRaw = [x[1]! * helper[2]! - x[2]! * helper[1]!, x[2]! * helper[0]! - x[0]! * helper[2]!, x[0]! * helper[1]! - x[1]! * helper[0]!]\n const zl = Math.hypot(...zRaw)\n const z = zRaw.map(c => c / zl)\n const y = [z[1]! * x[2]! - z[2]! * x[1]!, z[2]! * x[0]! - z[0]! * x[2]!, z[0]! * x[1]! - z[1]! * x[0]!]\n ghost.push({\n id: `${TARGET_FACE}:slide`,\n shape: boxShape(length, THICKNESS, THICKNESS),\n pose: { r: [0, 1, 2].map(i => [x[i]!, y[i]!, z[i]!]), t: [0, 1, 2].map(i => (from[i]! + to[i]!) / 2) },\n material: { token: TARGET_FACE }\n })\n }\n }\n return { control, min: input.min, max: input.max, unit: input.unit, ghost }\n}\n"]}
1
+ {"version":3,"file":"motion-ends.js","sourceRoot":"","sources":["../../client/modeller/motion-ends.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileFigureAsset, figureMotionsOf, motionStateIdOf } from '@hatiolab/figure-model'\n\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { boxShape } from './drawn-shapes.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * Where a part's motion takes it (ADR-0097 §5.5, designer 2026-09-27): the bodies its control moves, drawn translucent at\n * the control's lowest and highest value -- evaluated there by figure-model, not guessed -- and, for a slide, the line it\n * travels. Only the two ends are shown; nothing here says what lies between them is clear.\n */\n\ntype Pose = { r: number[][]; t: number[] }\ntype Body = FigureRenderPart & { pose: Pose }\n\n/** The line a slide travels along, thin enough not to hide the part. */\nconst THICKNESS = 6\n\nexport interface MotionEnds {\n control: string\n min: number\n max: number\n unit: string\n /** The moving bodies at the lowest value, then at the highest. */\n ghost: FigureRenderPart[]\n}\n\nconst samePose = (a: Pose, b: Pose) => [0, 1, 2].every(i => Math.abs(a.t[i]! - b.t[i]!) < 1e-6 && [0, 1, 2].every(k => Math.abs(a.r[i]![k]! - b.r[i]![k]!) < 1e-9))\n\n/**\n * The ends of the selected part's own motion -- its joint's first axis (ADR-0159) -- or nothing: a part that does not\n * move, follows another, or turns without end.\n */\nexport function motionEndsOf(revision: FigureRevision, part: string): MotionEnds | null {\n const asset = revision.asset\n const joint = asset.joints?.find(j => j.body1.part === part)\n const axis = joint && figureMotionsOf(joint)[0]\n /* A turn without end is a revolute axis whose control has no range: the range check below leaves it out. */\n if (!axis || axis.mimic) return null\n const control = motionStateIdOf(axis)\n const input = asset.document.model.inputs.find(i => i.id === control && i.role === 'state')\n if (!input || typeof input.min !== 'number' || typeof input.max !== 'number' || input.min === input.max) return null\n /* The version's design values are its asset's; its pose is every control as set now, this one at each end. */\n const state = Object.fromEntries(Object.entries(revision.inputs).filter(([id]) => id in asset.stateDefaults))\n const at = (value: number) => {\n try {\n return compileFigureAsset(asset).evaluate({ ...state, [control]: value }).geometry as unknown as Body[]\n } catch {\n return null\n }\n }\n const low = at(input.min)\n const high = at(input.max)\n if (!low || !high) return null\n const byId = new Map(high.filter(b => b.pose).map(b => [b.id, b]))\n const moving = low.filter(b => b.pose && 'shape' in b && byId.has(b.id) && !samePose(b.pose, byId.get(b.id)!.pose))\n if (!moving.length) return null\n const ghost: FigureRenderPart[] = [\n ...moving.map(b => ({ ...b, id: `${PREVIEW}:min:${b.id}`, material: { token: PREVIEW, transparent: true } })),\n ...moving.map(b => ({ ...byId.get(b.id)!, id: `${PREVIEW}:max:${b.id}`, material: { token: PREVIEW, transparent: true } }))\n ]\n /* A slide: the line its own body travels, from the lowest value to the highest. */\n const own = moving.find(b => b.id === part)\n if (axis.type === 'prismatic' && own) {\n const from = own.pose.t\n const to = byId.get(part)!.pose.t\n const v = [0, 1, 2].map(i => to[i]! - from[i]!)\n const length = Math.hypot(...v)\n if (length > 1e-6) {\n const x = v.map(c => c / length)\n /* Any two directions square to it and to each other complete its frame. */\n const helper = Math.abs(x[1]!) < 0.9 ? [0, 1, 0] : [1, 0, 0]\n const zRaw = [x[1]! * helper[2]! - x[2]! * helper[1]!, x[2]! * helper[0]! - x[0]! * helper[2]!, x[0]! * helper[1]! - x[1]! * helper[0]!]\n const zl = Math.hypot(...zRaw)\n const z = zRaw.map(c => c / zl)\n const y = [z[1]! * x[2]! - z[2]! * x[1]!, z[2]! * x[0]! - z[0]! * x[2]!, z[0]! * x[1]! - z[1]! * x[0]!]\n ghost.push({\n id: `${TARGET_FACE}:slide`,\n shape: boxShape(length, THICKNESS, THICKNESS),\n pose: { r: [0, 1, 2].map(i => [x[i]!, y[i]!, z[i]!]), t: [0, 1, 2].map(i => (from[i]! + to[i]!) / 2) },\n material: { token: TARGET_FACE }\n })\n }\n }\n return { control, min: input.min, max: input.max, unit: input.unit, ghost }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import type { FigureAsset, FigureAuthoringAction, FigureDimensionSource } from '@hatiolab/figure-model';
2
+ import type { FigureRevision } from './revision.js';
3
+ import { type Axis } from './links.js';
4
+ export type SizeMode = 'ratio' | 'difference' | 'custom';
5
+ export type SizeSource = Extract<FigureDimensionSource, {
6
+ of: 'instance' | 'part';
7
+ }>;
8
+ export declare const measureFactor: (asset: FigureAsset, part: string, axis: Axis) => number;
9
+ /** Only expose direct own inputs (including the canonical identity wrapper). Never overwrite an arbitrary expression. */
10
+ export declare function sizeInputOf(asset: FigureAsset, part: string, axis: Axis): string | undefined;
11
+ export declare function sizeRelationOf(revision: FigureRevision, part: string, axis: Axis): {
12
+ source?: SizeSource;
13
+ mode: SizeMode;
14
+ variable: boolean;
15
+ };
16
+ /**
17
+ * What a measure follows when it is first made to follow (ADR-0159 3): what the part hangs from, along the axis its own
18
+ * axis lies on as it stands -- a roller laid on its side follows the frame's depth with its length, not the frame's
19
+ * height (the axes paired by figure-model, `figureAxisPairsOf`, never by their letters). On the mounting plane, or
20
+ * fastened to nothing, the figure's size. Nothing when it lies at an angle, or the parent has no measure along that
21
+ * axis: the author picks. Only a suggestion when a relation is made; a measure that already follows something keeps it.
22
+ */
23
+ export declare function suggestedSizeSource(asset: FigureAsset, part: string, axis: Axis): SizeSource | undefined;
24
+ /** Whether two sources are one: the same part, or the figure, and the same axis. */
25
+ export declare const sameSizeSource: (a: SizeSource | undefined, b: SizeSource | undefined) => boolean;
26
+ /** How long the source is now, in millimetres as the panel shows it (a radius read as a diameter). */
27
+ export declare function sourceMeasureOf(revision: FigureRevision, source: SizeSource): number | undefined;
28
+ /** Preserve the displayed size when choosing a source or mode. mm edits change only that row's relation. */
29
+ export declare function sizeRelationSteps(revision: FigureRevision, part: string, axis: Axis, source: SizeSource, mode: Exclude<SizeMode, 'custom'>, mm?: number | undefined): FigureAuthoringAction[];
@@ -0,0 +1,99 @@
1
+ import { figureAxisPairsOf, figurePartsExtentOf, figureShapeProviderOf } from '@hatiolab/figure-model';
2
+ import { dimensionNow, followOf, followableAxes, measureNow } from './links.js';
3
+ function editOf(asset, part, axis) {
4
+ const shape = asset.document.model.nodes.find(n => n.id === `${part}.shape`);
5
+ const edit = shape && figureShapeProviderOf(shape)?.edit[axis];
6
+ return shape && edit && !('refused' in edit) ? { shape, edit } : undefined;
7
+ }
8
+ export const measureFactor = (asset, part, axis) => editOf(asset, part, axis)?.edit.k ?? 1;
9
+ /** Only expose direct own inputs (including the canonical identity wrapper). Never overwrite an arbitrary expression. */
10
+ export function sizeInputOf(asset, part, axis) {
11
+ const e = editOf(asset, part, axis);
12
+ if (!e)
13
+ return undefined;
14
+ const model = asset.document.model;
15
+ let ref = e.shape.args[e.edit.arg];
16
+ const seen = new Set();
17
+ while (ref && !seen.has(ref)) {
18
+ seen.add(ref);
19
+ if (model.inputs.some(i => i.id === ref && i.role === 'design' && i.unit === 'mm') && ref.startsWith(`${part}.`))
20
+ return ref;
21
+ const writer = model.nodes.find(n => Object.values(n.outputs).includes(ref));
22
+ if (writer?.op !== 'mul@1' || model.constants.find(c => c.id === writer.args[1])?.value !== 1)
23
+ return undefined;
24
+ ref = writer.args[0];
25
+ }
26
+ }
27
+ export function sizeRelationOf(revision, part, axis) {
28
+ const f = followOf(revision.asset, part, axis);
29
+ if (f.of === 'own')
30
+ return { mode: 'ratio', variable: false };
31
+ if (f.of !== 'instance' && f.of !== 'part')
32
+ return { mode: 'custom', variable: true };
33
+ const source = f.of === 'instance' ? { of: 'instance', axis: f.axis } : { of: 'part', part: f.part, axis: f.axis };
34
+ const equalFactor = (source.of === 'part' ? measureFactor(revision.asset, source.part, source.axis) : 1) / measureFactor(revision.asset, part, axis);
35
+ const mode = !(f.plus ?? 0) ? 'ratio' : Math.abs((f.times ?? 1) - equalFactor) < 1e-6 ? 'difference' : 'custom';
36
+ return { source, mode, variable: true };
37
+ }
38
+ /**
39
+ * What a measure follows when it is first made to follow (ADR-0159 3): what the part hangs from, along the axis its own
40
+ * axis lies on as it stands -- a roller laid on its side follows the frame's depth with its length, not the frame's
41
+ * height (the axes paired by figure-model, `figureAxisPairsOf`, never by their letters). On the mounting plane, or
42
+ * fastened to nothing, the figure's size. Nothing when it lies at an angle, or the parent has no measure along that
43
+ * axis: the author picks. Only a suggestion when a relation is made; a measure that already follows something keeps it.
44
+ */
45
+ export function suggestedSizeSource(asset, part, axis) {
46
+ const { parent, along } = figureAxisPairsOf(asset, part);
47
+ const on = along[axis]?.axis;
48
+ if (!on)
49
+ return undefined;
50
+ if (!parent)
51
+ return { of: 'instance', axis: on };
52
+ return followableAxes(asset, parent).includes(on) ? { of: 'part', part: parent, axis: on } : undefined;
53
+ }
54
+ /** Whether two sources are one: the same part, or the figure, and the same axis. */
55
+ export const sameSizeSource = (a, b) => !!a && !!b && a.of === b.of && a.axis === b.axis && (a.of !== 'part' || (b.of === 'part' && a.part === b.part));
56
+ /** How long the source is now, in millimetres as the panel shows it (a radius read as a diameter). */
57
+ export function sourceMeasureOf(revision, source) {
58
+ if (source.of === 'instance')
59
+ return revision.asset.designInputs[`size.${source.axis}`] ?? figurePartsExtentOf(revision.asset)[source.axis];
60
+ return measureNow(revision, source.part, source.axis);
61
+ }
62
+ /** Preserve the displayed size when choosing a source or mode. mm edits change only that row's relation. */
63
+ export function sizeRelationSteps(revision, part, axis, source, mode, mm = measureNow(revision, part, axis)) {
64
+ if (!(typeof mm === 'number' && Number.isFinite(mm) && mm > 0))
65
+ throw Error('SIZE_VALUE');
66
+ if (source.of === 'part' && source.part === part)
67
+ throw Error('SIZE_SOURCE');
68
+ const asset = revision.asset;
69
+ const steps = [];
70
+ let raw;
71
+ let sourceFactor = 1;
72
+ if (source.of === 'instance') {
73
+ const declared = asset.document.model.inputs.some(i => i.id === `size.${source.axis}` && i.role === 'design');
74
+ if (declared)
75
+ raw = asset.designInputs[`size.${source.axis}`];
76
+ else {
77
+ // Do not replace an existing partial size declaration while configuring an unrelated axis.
78
+ if (asset.document.model.inputs.some(i => /^size\.[xyz]$/.test(i.id)))
79
+ throw Error('SIZE_SOURCE');
80
+ const at = figurePartsExtentOf(asset);
81
+ steps.push({ kind: 'declare-instance-size', at });
82
+ raw = at[source.axis];
83
+ }
84
+ }
85
+ else {
86
+ if (!followableAxes(asset, source.part).includes(source.axis))
87
+ throw Error('SIZE_SOURCE');
88
+ raw = dimensionNow(revision, source.part, source.axis);
89
+ sourceFactor = measureFactor(asset, source.part, source.axis);
90
+ }
91
+ if (!(typeof raw === 'number' && raw > 0 && Number.isFinite(raw)))
92
+ throw Error('SIZE_SOURCE');
93
+ const ownFactor = measureFactor(asset, part, axis);
94
+ const times = mode === 'ratio' ? mm / ownFactor / raw : sourceFactor / ownFactor;
95
+ const plus = mode === 'difference' ? (mm - raw * sourceFactor) / ownFactor : 0;
96
+ steps.push({ kind: 'link-dimension', part, axis, source, times, ...(plus ? { plus } : {}), replace: true });
97
+ return steps;
98
+ }
99
+ //# sourceMappingURL=size-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size-response.js","sourceRoot":"","sources":["../../client/modeller/size-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAGtG,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAa,MAAM,YAAY,CAAA;AAK1F,SAAS,MAAM,CAAC,KAAkB,EAAE,IAAY,EAAE,IAAU;IAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,QAAQ,CAAC,CAAA;IAC5E,MAAM,IAAI,GAAG,KAAK,IAAI,qBAAqB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9D,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5E,CAAC;AACD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAkB,EAAE,IAAY,EAAE,IAAU,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAA;AAErH,yHAAyH;AACzH,MAAM,UAAU,WAAW,CAAC,KAAkB,EAAE,IAAY,EAAE,IAAU;IACtE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAA;IAClC,IAAI,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC;YAAE,OAAO,GAAG,CAAA;QAC5H,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAI,CAAC,CAAC,CAAA;QAC7E,IAAI,MAAM,EAAE,EAAE,KAAK,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QAC/G,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC;AACH,CAAC;AACD,MAAM,UAAU,cAAc,CAAC,QAAwB,EAAE,IAAY,EAAE,IAAU;IAC/E,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IAC9C,IAAI,CAAC,CAAC,EAAE,KAAK,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC7D,IAAI,CAAC,CAAC,EAAE,KAAK,UAAU,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACrF,MAAM,MAAM,GAAe,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC9H,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACpJ,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC/G,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAkB,EAAE,IAAY,EAAE,IAAU;IAC9E,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACxD,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAA;IAC5B,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAA;IACzB,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;IAChD,OAAO,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACxG,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAyB,EAAE,CAAyB,EAAE,EAAE,CACrF,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAEjH,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAAC,QAAwB,EAAE,MAAkB;IAC1E,IAAI,MAAM,CAAC,EAAE,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC3I,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;AACvD,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,iBAAiB,CAAC,QAAwB,EAAE,IAAY,EAAE,IAAU,EAAE,MAAkB,EAAE,IAAiC,EAAE,EAAE,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;IAChL,IAAI,CAAC,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAAE,MAAM,KAAK,CAAC,YAAY,CAAC,CAAA;IACzF,IAAI,MAAM,CAAC,EAAE,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;QAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;IAC5E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAC5B,MAAM,KAAK,GAA4B,EAAE,CAAA;IACzC,IAAI,GAAuB,CAAA;IAC3B,IAAI,YAAY,GAAG,CAAC,CAAA;IACpB,IAAI,MAAM,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;QAC7G,IAAI,QAAQ;YAAE,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;aACxD,CAAC;YACJ,2FAA2F;YAC3F,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;YACjG,MAAM,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;YACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,EAAE,EAAE,CAAC,CAAA;YACjD,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;QACzF,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACtD,YAAY,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IAC/D,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;IAC7F,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IAClD,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,GAAG,SAAS,CAAA;IAChF,MAAM,IAAI,GAAG,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3G,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["import { figureAxisPairsOf, figurePartsExtentOf, figureShapeProviderOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureAuthoringAction, FigureDimensionSource } from '@hatiolab/figure-model'\nimport type { FigureRevision } from './revision.js'\nimport { dimensionNow, followOf, followableAxes, measureNow, type Axis } from './links.js'\n\nexport type SizeMode = 'ratio' | 'difference' | 'custom'\nexport type SizeSource = Extract<FigureDimensionSource, { of: 'instance' | 'part' }>\n\nfunction editOf(asset: FigureAsset, part: string, axis: Axis) {\n const shape = asset.document.model.nodes.find(n => n.id === `${part}.shape`)\n const edit = shape && figureShapeProviderOf(shape)?.edit[axis]\n return shape && edit && !('refused' in edit) ? { shape, edit } : undefined\n}\nexport const measureFactor = (asset: FigureAsset, part: string, axis: Axis) => editOf(asset, part, axis)?.edit.k ?? 1\n\n/** Only expose direct own inputs (including the canonical identity wrapper). Never overwrite an arbitrary expression. */\nexport function sizeInputOf(asset: FigureAsset, part: string, axis: Axis): string | undefined {\n const e = editOf(asset, part, axis)\n if (!e) return undefined\n const model = asset.document.model\n let ref = e.shape.args[e.edit.arg]\n const seen = new Set<string>()\n while (ref && !seen.has(ref)) {\n seen.add(ref)\n if (model.inputs.some(i => i.id === ref && i.role === 'design' && i.unit === 'mm') && ref.startsWith(`${part}.`)) return ref\n const writer = model.nodes.find(n => Object.values(n.outputs).includes(ref!))\n if (writer?.op !== 'mul@1' || model.constants.find(c => c.id === writer.args[1])?.value !== 1) return undefined\n ref = writer.args[0]\n }\n}\nexport function sizeRelationOf(revision: FigureRevision, part: string, axis: Axis): { source?: SizeSource; mode: SizeMode; variable: boolean } {\n const f = followOf(revision.asset, part, axis)\n if (f.of === 'own') return { mode: 'ratio', variable: false }\n if (f.of !== 'instance' && f.of !== 'part') return { mode: 'custom', variable: true }\n const source: SizeSource = f.of === 'instance' ? { of: 'instance', axis: f.axis } : { of: 'part', part: f.part, axis: f.axis }\n const equalFactor = (source.of === 'part' ? measureFactor(revision.asset, source.part, source.axis) : 1) / measureFactor(revision.asset, part, axis)\n const mode = !(f.plus ?? 0) ? 'ratio' : Math.abs((f.times ?? 1) - equalFactor) < 1e-6 ? 'difference' : 'custom'\n return { source, mode, variable: true }\n}\n\n/**\n * What a measure follows when it is first made to follow (ADR-0159 3): what the part hangs from, along the axis its own\n * axis lies on as it stands -- a roller laid on its side follows the frame's depth with its length, not the frame's\n * height (the axes paired by figure-model, `figureAxisPairsOf`, never by their letters). On the mounting plane, or\n * fastened to nothing, the figure's size. Nothing when it lies at an angle, or the parent has no measure along that\n * axis: the author picks. Only a suggestion when a relation is made; a measure that already follows something keeps it.\n */\nexport function suggestedSizeSource(asset: FigureAsset, part: string, axis: Axis): SizeSource | undefined {\n const { parent, along } = figureAxisPairsOf(asset, part)\n const on = along[axis]?.axis\n if (!on) return undefined\n if (!parent) return { of: 'instance', axis: on }\n return followableAxes(asset, parent).includes(on) ? { of: 'part', part: parent, axis: on } : undefined\n}\n\n/** Whether two sources are one: the same part, or the figure, and the same axis. */\nexport const sameSizeSource = (a: SizeSource | undefined, b: SizeSource | undefined) =>\n !!a && !!b && a.of === b.of && a.axis === b.axis && (a.of !== 'part' || (b.of === 'part' && a.part === b.part))\n\n/** How long the source is now, in millimetres as the panel shows it (a radius read as a diameter). */\nexport function sourceMeasureOf(revision: FigureRevision, source: SizeSource): number | undefined {\n if (source.of === 'instance') return revision.asset.designInputs[`size.${source.axis}`] ?? figurePartsExtentOf(revision.asset)[source.axis]\n return measureNow(revision, source.part, source.axis)\n}\n\n/** Preserve the displayed size when choosing a source or mode. mm edits change only that row's relation. */\nexport function sizeRelationSteps(revision: FigureRevision, part: string, axis: Axis, source: SizeSource, mode: Exclude<SizeMode, 'custom'>, mm = measureNow(revision, part, axis)): FigureAuthoringAction[] {\n if (!(typeof mm === 'number' && Number.isFinite(mm) && mm > 0)) throw Error('SIZE_VALUE')\n if (source.of === 'part' && source.part === part) throw Error('SIZE_SOURCE')\n const asset = revision.asset\n const steps: FigureAuthoringAction[] = []\n let raw: number | undefined\n let sourceFactor = 1\n if (source.of === 'instance') {\n const declared = asset.document.model.inputs.some(i => i.id === `size.${source.axis}` && i.role === 'design')\n if (declared) raw = asset.designInputs[`size.${source.axis}`]\n else {\n // Do not replace an existing partial size declaration while configuring an unrelated axis.\n if (asset.document.model.inputs.some(i => /^size\\.[xyz]$/.test(i.id))) throw Error('SIZE_SOURCE')\n const at = figurePartsExtentOf(asset)\n steps.push({ kind: 'declare-instance-size', at })\n raw = at[source.axis]\n }\n } else {\n if (!followableAxes(asset, source.part).includes(source.axis)) throw Error('SIZE_SOURCE')\n raw = dimensionNow(revision, source.part, source.axis)\n sourceFactor = measureFactor(asset, source.part, source.axis)\n }\n if (!(typeof raw === 'number' && raw > 0 && Number.isFinite(raw))) throw Error('SIZE_SOURCE')\n const ownFactor = measureFactor(asset, part, axis)\n const times = mode === 'ratio' ? mm / ownFactor / raw : sourceFactor / ownFactor\n const plus = mode === 'difference' ? (mm - raw * sourceFactor) / ownFactor : 0\n steps.push({ kind: 'link-dimension', part, axis, source, times, ...(plus ? { plus } : {}), replace: true })\n return steps\n}\n"]}