@cardstack/choreo 0.0.0 → 0.1.0-unstable.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -6
  4. package/addon-main.cjs +4 -0
  5. package/declarations/anchors.d.ts +26 -0
  6. package/declarations/anchors.d.ts.map +1 -0
  7. package/declarations/arming.d.ts +33 -0
  8. package/declarations/arming.d.ts.map +1 -0
  9. package/declarations/beacon.d.ts +9 -0
  10. package/declarations/beacon.d.ts.map +1 -0
  11. package/declarations/beacons.d.ts +23 -0
  12. package/declarations/beacons.d.ts.map +1 -0
  13. package/declarations/changeset.d.ts +46 -0
  14. package/declarations/changeset.d.ts.map +1 -0
  15. package/declarations/choreo.d.ts +261 -0
  16. package/declarations/choreo.d.ts.map +1 -0
  17. package/declarations/compile.d.ts +45 -0
  18. package/declarations/compile.d.ts.map +1 -0
  19. package/declarations/deliver.d.ts +44 -0
  20. package/declarations/deliver.d.ts.map +1 -0
  21. package/declarations/easings.d.ts +20 -0
  22. package/declarations/easings.d.ts.map +1 -0
  23. package/declarations/far.d.ts +33 -0
  24. package/declarations/far.d.ts.map +1 -0
  25. package/declarations/film/clip.d.ts +51 -0
  26. package/declarations/film/clip.d.ts.map +1 -0
  27. package/declarations/film/clips.d.ts +155 -0
  28. package/declarations/film/clips.d.ts.map +1 -0
  29. package/declarations/film/film.d.ts +1208 -0
  30. package/declarations/film/film.d.ts.map +1 -0
  31. package/declarations/film/graph/adjust.d.ts +112 -0
  32. package/declarations/film/graph/adjust.d.ts.map +1 -0
  33. package/declarations/film/graph/compile.d.ts +140 -0
  34. package/declarations/film/graph/compile.d.ts.map +1 -0
  35. package/declarations/film/graph/host.d.ts +67 -0
  36. package/declarations/film/graph/host.d.ts.map +1 -0
  37. package/declarations/film/graph/nodes.d.ts +287 -0
  38. package/declarations/film/graph/nodes.d.ts.map +1 -0
  39. package/declarations/film/index.d.ts +24 -0
  40. package/declarations/film/index.d.ts.map +1 -0
  41. package/declarations/film/joins.d.ts +143 -0
  42. package/declarations/film/joins.d.ts.map +1 -0
  43. package/declarations/film/math.d.ts +15 -0
  44. package/declarations/film/math.d.ts.map +1 -0
  45. package/declarations/film/overlays.d.ts +54 -0
  46. package/declarations/film/overlays.d.ts.map +1 -0
  47. package/declarations/film/picture.d.ts +104 -0
  48. package/declarations/film/picture.d.ts.map +1 -0
  49. package/declarations/film/plate.d.ts +37 -0
  50. package/declarations/film/plate.d.ts.map +1 -0
  51. package/declarations/film/player.d.ts +120 -0
  52. package/declarations/film/player.d.ts.map +1 -0
  53. package/declarations/film/rail.d.ts +40 -0
  54. package/declarations/film/rail.d.ts.map +1 -0
  55. package/declarations/film/schedule.d.ts +93 -0
  56. package/declarations/film/schedule.d.ts.map +1 -0
  57. package/declarations/film/seam.d.ts +38 -0
  58. package/declarations/film/seam.d.ts.map +1 -0
  59. package/declarations/film/titles.d.ts +37 -0
  60. package/declarations/film/titles.d.ts.map +1 -0
  61. package/declarations/film/types.d.ts +544 -0
  62. package/declarations/film/types.d.ts.map +1 -0
  63. package/declarations/film.d.ts +3 -0
  64. package/declarations/film.d.ts.map +1 -0
  65. package/declarations/gesture.d.ts +33 -0
  66. package/declarations/gesture.d.ts.map +1 -0
  67. package/declarations/index.d.ts +26 -0
  68. package/declarations/index.d.ts.map +1 -0
  69. package/declarations/measure.d.ts +29 -0
  70. package/declarations/measure.d.ts.map +1 -0
  71. package/declarations/path.d.ts +73 -0
  72. package/declarations/path.d.ts.map +1 -0
  73. package/declarations/registry.d.ts +46 -0
  74. package/declarations/registry.d.ts.map +1 -0
  75. package/declarations/run.d.ts +406 -0
  76. package/declarations/run.d.ts.map +1 -0
  77. package/declarations/space.d.ts +31 -0
  78. package/declarations/space.d.ts.map +1 -0
  79. package/declarations/steps.d.ts +479 -0
  80. package/declarations/steps.d.ts.map +1 -0
  81. package/declarations/test-support/index.d.ts +30 -0
  82. package/declarations/test-support/index.d.ts.map +1 -0
  83. package/declarations/types.d.ts +761 -0
  84. package/declarations/types.d.ts.map +1 -0
  85. package/dist/anchors.js +34 -0
  86. package/dist/anchors.js.map +1 -0
  87. package/dist/arming.js +120 -0
  88. package/dist/arming.js.map +1 -0
  89. package/dist/beacon.js +25 -0
  90. package/dist/beacon.js.map +1 -0
  91. package/dist/beacons.js +77 -0
  92. package/dist/beacons.js.map +1 -0
  93. package/dist/changeset.js +129 -0
  94. package/dist/changeset.js.map +1 -0
  95. package/dist/choreo.js +1026 -0
  96. package/dist/choreo.js.map +1 -0
  97. package/dist/compile.js +1403 -0
  98. package/dist/compile.js.map +1 -0
  99. package/dist/deliver.js +326 -0
  100. package/dist/deliver.js.map +1 -0
  101. package/dist/easings.js +39 -0
  102. package/dist/easings.js.map +1 -0
  103. package/dist/far.js +147 -0
  104. package/dist/far.js.map +1 -0
  105. package/dist/film/clip.js +100 -0
  106. package/dist/film/clip.js.map +1 -0
  107. package/dist/film/clips.js +147 -0
  108. package/dist/film/clips.js.map +1 -0
  109. package/dist/film/film.js +4424 -0
  110. package/dist/film/film.js.map +1 -0
  111. package/dist/film/graph/adjust.js +160 -0
  112. package/dist/film/graph/adjust.js.map +1 -0
  113. package/dist/film/graph/compile.js +225 -0
  114. package/dist/film/graph/compile.js.map +1 -0
  115. package/dist/film/graph/host.js +77 -0
  116. package/dist/film/graph/host.js.map +1 -0
  117. package/dist/film/graph/nodes.js +500 -0
  118. package/dist/film/graph/nodes.js.map +1 -0
  119. package/dist/film/index.js +18 -0
  120. package/dist/film/index.js.map +1 -0
  121. package/dist/film/joins.js +260 -0
  122. package/dist/film/joins.js.map +1 -0
  123. package/dist/film/math.js +49 -0
  124. package/dist/film/math.js.map +1 -0
  125. package/dist/film/overlays.js +66 -0
  126. package/dist/film/overlays.js.map +1 -0
  127. package/dist/film/picture.js +58 -0
  128. package/dist/film/picture.js.map +1 -0
  129. package/dist/film/plate.js +36 -0
  130. package/dist/film/plate.js.map +1 -0
  131. package/dist/film/player.js +79 -0
  132. package/dist/film/player.js.map +1 -0
  133. package/dist/film/rail.js +38 -0
  134. package/dist/film/rail.js.map +1 -0
  135. package/dist/film/schedule.js +238 -0
  136. package/dist/film/schedule.js.map +1 -0
  137. package/dist/film/seam.js +64 -0
  138. package/dist/film/seam.js.map +1 -0
  139. package/dist/film/titles.js +45 -0
  140. package/dist/film/titles.js.map +1 -0
  141. package/dist/film/types.js +2 -0
  142. package/dist/film/types.js.map +1 -0
  143. package/dist/film.js +18 -0
  144. package/dist/film.js.map +1 -0
  145. package/dist/gesture.js +111 -0
  146. package/dist/gesture.js.map +1 -0
  147. package/dist/index.js +9 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/measure.js +99 -0
  150. package/dist/measure.js.map +1 -0
  151. package/dist/path.js +272 -0
  152. package/dist/path.js.map +1 -0
  153. package/dist/registry.js +55 -0
  154. package/dist/registry.js.map +1 -0
  155. package/dist/run.js +2423 -0
  156. package/dist/run.js.map +1 -0
  157. package/dist/space.js +57 -0
  158. package/dist/space.js.map +1 -0
  159. package/dist/steps.js +831 -0
  160. package/dist/steps.js.map +1 -0
  161. package/dist/test-support/index.js +150 -0
  162. package/dist/test-support/index.js.map +1 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/package.json +201 -6
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `c.gesture` — the live drag as a first-class geometry source (§6.1, §6.4).
3
+ *
4
+ * A document-level pointer tracker (listeners, not timers) keeps the last
5
+ * pointer position and a short velocity window. A Move that borrows
6
+ * `{{c.gesture}}` as its `@from` starts from wherever the finger let go —
7
+ * sized as the sprite, centred on the pointer — and any spring that flies
8
+ * it inherits the pointer's velocity, so a toss leaves at the speed it was
9
+ * thrown.
10
+ */
11
+
12
+ const GESTURE = {
13
+ gesture: true
14
+ };
15
+ function isGestureRef(v) {
16
+ return typeof v === 'object' && v !== null && v.gesture === true;
17
+ }
18
+ let samples = [];
19
+ let tracking = false;
20
+ function record(event) {
21
+ const now = performance.now();
22
+ samples.push({
23
+ t: now,
24
+ x: event.clientX,
25
+ y: event.clientY
26
+ });
27
+ if (samples.length > 8) {
28
+ samples.shift();
29
+ }
30
+ }
31
+
32
+ /** idempotent; the first <Choreo> turns it on */
33
+ function trackGestures() {
34
+ if (tracking || typeof window === 'undefined') {
35
+ return;
36
+ }
37
+ tracking = true;
38
+ window.addEventListener('pointerdown', record, {
39
+ capture: true,
40
+ passive: true
41
+ });
42
+ window.addEventListener('pointermove', record, {
43
+ capture: true,
44
+ passive: true
45
+ });
46
+ window.addEventListener('pointerup', record, {
47
+ capture: true,
48
+ passive: true
49
+ });
50
+ }
51
+
52
+ /** the pointer's page position now, or null before any pointer event */
53
+ function gesturePoint() {
54
+ const last = samples[samples.length - 1];
55
+ return last ? {
56
+ x: last.x,
57
+ y: last.y
58
+ } : null;
59
+ }
60
+
61
+ /** units per second over the recent window — what a spring inherits */
62
+ function gestureVelocity() {
63
+ if (samples.length < 2) {
64
+ return {
65
+ x: 0,
66
+ y: 0
67
+ };
68
+ }
69
+ const now = performance.now();
70
+ const recent = samples.filter(s => now - s.t < 120);
71
+ const a = recent[0] ?? samples[samples.length - 2];
72
+ const b = samples[samples.length - 1];
73
+ const dt = (b.t - a.t) / 1000;
74
+ if (dt <= 0) {
75
+ return {
76
+ x: 0,
77
+ y: 0
78
+ };
79
+ }
80
+ return {
81
+ x: (b.x - a.x) / dt,
82
+ y: (b.y - a.y) / dt
83
+ };
84
+ }
85
+
86
+ /** the gesture as a box: the sprite\'s own size, centred on the pointer */
87
+ function gestureBounds(size) {
88
+ const point = gesturePoint();
89
+ if (!point) {
90
+ return null;
91
+ }
92
+ const page = {
93
+ height: size.height,
94
+ width: size.width,
95
+ x: point.x - size.width / 2,
96
+ y: point.y - size.height / 2
97
+ };
98
+ return {
99
+ context: page,
100
+ page,
101
+ parent: page
102
+ };
103
+ }
104
+
105
+ /** test-support: forget everything between tests */
106
+ function resetGestures() {
107
+ samples = [];
108
+ }
109
+
110
+ export { GESTURE, gestureBounds, gesturePoint, gestureVelocity, isGestureRef, resetGestures, trackGestures };
111
+ //# sourceMappingURL=gesture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gesture.js","sources":["../src/gesture.ts"],"sourcesContent":["/**\n * `c.gesture` — the live drag as a first-class geometry source (§6.1, §6.4).\n *\n * A document-level pointer tracker (listeners, not timers) keeps the last\n * pointer position and a short velocity window. A Move that borrows\n * `{{c.gesture}}` as its `@from` starts from wherever the finger let go —\n * sized as the sprite, centred on the pointer — and any spring that flies\n * it inherits the pointer's velocity, so a toss leaves at the speed it was\n * thrown.\n */\nimport type { Bounds, Rect } from './types.ts';\n\nexport interface GestureRef {\n gesture: true;\n}\n\nexport const GESTURE: GestureRef = { gesture: true };\n\nexport function isGestureRef(v: unknown): v is GestureRef {\n return (\n typeof v === 'object' &&\n v !== null &&\n (v as { gesture?: unknown }).gesture === true\n );\n}\n\ninterface Sample {\n t: number;\n x: number;\n y: number;\n}\n\nlet samples: Sample[] = [];\nlet tracking = false;\n\nfunction record(event: PointerEvent) {\n const now = performance.now();\n samples.push({ t: now, x: event.clientX, y: event.clientY });\n if (samples.length > 8) {\n samples.shift();\n }\n}\n\n/** idempotent; the first <Choreo> turns it on */\nexport function trackGestures() {\n if (tracking || typeof window === 'undefined') {\n return;\n }\n tracking = true;\n window.addEventListener('pointerdown', record, {\n capture: true,\n passive: true,\n });\n window.addEventListener('pointermove', record, {\n capture: true,\n passive: true,\n });\n window.addEventListener('pointerup', record, {\n capture: true,\n passive: true,\n });\n}\n\n/** the pointer's page position now, or null before any pointer event */\nexport function gesturePoint(): { x: number; y: number } | null {\n const last = samples[samples.length - 1];\n return last ? { x: last.x, y: last.y } : null;\n}\n\n/** units per second over the recent window — what a spring inherits */\nexport function gestureVelocity(): { x: number; y: number } {\n if (samples.length < 2) {\n return { x: 0, y: 0 };\n }\n const now = performance.now();\n const recent = samples.filter((s) => now - s.t < 120);\n const a = recent[0] ?? samples[samples.length - 2]!;\n const b = samples[samples.length - 1]!;\n const dt = (b.t - a.t) / 1000;\n if (dt <= 0) {\n return { x: 0, y: 0 };\n }\n return { x: (b.x - a.x) / dt, y: (b.y - a.y) / dt };\n}\n\n/** the gesture as a box: the sprite\\'s own size, centred on the pointer */\nexport function gestureBounds(size: Rect): Bounds | null {\n const point = gesturePoint();\n if (!point) {\n return null;\n }\n const page: Rect = {\n height: size.height,\n width: size.width,\n x: point.x - size.width / 2,\n y: point.y - size.height / 2,\n };\n return { context: page, page, parent: page };\n}\n\n/** test-support: forget everything between tests */\nexport function resetGestures() {\n samples = [];\n}\n"],"names":["GESTURE","gesture","isGestureRef","v","samples","tracking","record","event","now","performance","push","t","x","clientX","y","clientY","length","shift","trackGestures","window","addEventListener","capture","passive","gesturePoint","last","gestureVelocity","recent","filter","s","a","b","dt","gestureBounds","size","point","page","height","width","context","parent","resetGestures"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { after, at } from './anchors.js';
2
+ export { createArming } from './arming.js';
3
+ export { beacon } from './beacon.js';
4
+ export { Choreo } from './choreo.js';
5
+ export { easeIn, easeInAndOut, easeOut } from './easings.js';
6
+ export { choreoHostAt, choreoHostById, closestChoreo } from './registry.js';
7
+ export { appliedCamera, toLocal, toPage } from './space.js';
8
+ export { StepComponent, toMs } from './steps.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
@@ -0,0 +1,99 @@
1
+ import { defineParticipantArg } from 'glimmer-motion';
2
+
3
+ /**
4
+ * One measurement layer, for everything that measures.
5
+ *
6
+ * The region and the beacon registry both turn a DOMRect into the three spaces
7
+ * a step can ask for — page, region-relative, offset-parent-relative — and they
8
+ * have to agree exactly, because a Move routinely reads one sprite's `page` and
9
+ * another's. Ember Animated learned the same thing the hard way and ended up
10
+ * with a single bounds module; two copies of `minus` that drift by a scroll
11
+ * offset produce a flight that lands next to its target, which is the kind of
12
+ * bug that looks like a spring problem for a day.
13
+ */
14
+ // `pack`'s type is declared in registry.ts, which the published declarations reach
15
+ defineParticipantArg('pack', (el, pack) => {
16
+ if (pack === 'content') {
17
+ el.setAttribute('data-choreo-pack', 'content');
18
+ } else {
19
+ el.removeAttribute('data-choreo-pack');
20
+ }
21
+ });
22
+
23
+ /** a box in page space */
24
+ const rect = r => ({
25
+ height: r.height,
26
+ width: r.width,
27
+ x: r.left,
28
+ y: r.top
29
+ });
30
+
31
+ /** `a` expressed relative to `b`'s top-left, keeping a's size */
32
+ const minus = (a, b) => ({
33
+ height: a.height,
34
+ width: a.width,
35
+ x: a.left - b.left,
36
+ y: a.top - b.top
37
+ });
38
+
39
+ /** an element's box together with the box it is positioned inside */
40
+
41
+ /**
42
+ * The shrink-wrap of `el`'s contents — the ink — not the stretched layout
43
+ * box. A full-bleed title (`left:0; right:0; width:auto`) still has a
44
+ * word-sized range; Crossing crop must match THAT, or the type explodes
45
+ * into a viewport strip. Empty / collapsed → undefined, so the frame wins.
46
+ */
47
+ const inkBox = el => {
48
+ const doc = el.ownerDocument;
49
+ if (!doc) {
50
+ return undefined;
51
+ }
52
+ const range = doc.createRange();
53
+ range.selectNodeContents(el);
54
+ const r = range.getBoundingClientRect();
55
+ if (r.width < 0.5 && r.height < 0.5) {
56
+ return undefined;
57
+ }
58
+ return r;
59
+ };
60
+ const substanceOf = el => {
61
+ const marked = el.querySelector('[data-choreo-substance]')?.getBoundingClientRect();
62
+ if (marked) {
63
+ return marked;
64
+ }
65
+ if (el.getAttribute('data-choreo-pack') === 'content') {
66
+ return inkBox(el);
67
+ }
68
+ return undefined;
69
+ };
70
+
71
+ /** the containing block a `position: absolute` child would be placed against */
72
+ const offsetBox = el => (el.offsetParent ?? el.parentElement ?? el).getBoundingClientRect();
73
+ const measure = el => ({
74
+ el: el.getBoundingClientRect(),
75
+ // read with the geometry, while the element is still attached: a removed
76
+ // skin is detached DOM by the time a crossing asks what color it wore
77
+ paint: getComputedStyle(el).backgroundColor,
78
+ parent: offsetBox(el),
79
+ // Keynote matches OBJECTS, not slide frames: an element may declare its
80
+ // visible subject with [data-choreo-substance], or pack="content" (the
81
+ // shrink-wrap / ink, no extra node). Captured with the geometry for
82
+ // the same reason paint is.
83
+ substance: substanceOf(el)
84
+ });
85
+
86
+ /** the three spaces, from a snapshot and the region's own box */
87
+ const boundsOf = (snap, root) => ({
88
+ context: minus(snap.el, root),
89
+ page: rect(snap.el),
90
+ paint: snap.paint,
91
+ parent: minus(snap.el, snap.parent),
92
+ substance: snap.substance && rect(snap.substance)
93
+ });
94
+
95
+ /** measure an element straight into bounds — what a beacon needs */
96
+ const measureBounds = (el, root) => boundsOf(measure(el), root);
97
+
98
+ export { boundsOf, inkBox, measure, measureBounds, minus, offsetBox, rect };
99
+ //# sourceMappingURL=measure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure.js","sources":["../src/measure.ts"],"sourcesContent":["/**\n * One measurement layer, for everything that measures.\n *\n * The region and the beacon registry both turn a DOMRect into the three spaces\n * a step can ask for — page, region-relative, offset-parent-relative — and they\n * have to agree exactly, because a Move routinely reads one sprite's `page` and\n * another's. Ember Animated learned the same thing the hard way and ended up\n * with a single bounds module; two copies of `minus` that drift by a scroll\n * offset produce a flight that lands next to its target, which is the kind of\n * bug that looks like a spring problem for a day.\n */\nimport { defineParticipantArg } from 'glimmer-motion';\n\nimport type { Bounds, Rect } from './types.ts';\n\n// `pack`'s type is declared in registry.ts, which the published declarations reach\ndefineParticipantArg('pack', (el, pack) => {\n if (pack === 'content') {\n el.setAttribute('data-choreo-pack', 'content');\n } else {\n el.removeAttribute('data-choreo-pack');\n }\n});\n\n/** a box in page space */\nexport const rect = (r: DOMRect): Rect => ({\n height: r.height,\n width: r.width,\n x: r.left,\n y: r.top,\n});\n\n/** `a` expressed relative to `b`'s top-left, keeping a's size */\nexport const minus = (a: DOMRect, b: DOMRect): Rect => ({\n height: a.height,\n width: a.width,\n x: a.left - b.left,\n y: a.top - b.top,\n});\n\n/** an element's box together with the box it is positioned inside */\nexport interface Snapshot {\n el: DOMRect;\n /** the computed background at measure time — see Bounds.paint */\n paint?: string;\n parent: DOMRect;\n /** the declared subject's box at measure time — see Bounds.substance */\n substance?: DOMRect;\n}\n\n/**\n * The shrink-wrap of `el`'s contents — the ink — not the stretched layout\n * box. A full-bleed title (`left:0; right:0; width:auto`) still has a\n * word-sized range; Crossing crop must match THAT, or the type explodes\n * into a viewport strip. Empty / collapsed → undefined, so the frame wins.\n */\nexport const inkBox = (el: Element): DOMRect | undefined => {\n const doc = el.ownerDocument;\n if (!doc) {\n return undefined;\n }\n const range = doc.createRange();\n range.selectNodeContents(el);\n const r = range.getBoundingClientRect();\n if (r.width < 0.5 && r.height < 0.5) {\n return undefined;\n }\n return r;\n};\n\nconst substanceOf = (el: Element): DOMRect | undefined => {\n const marked = el\n .querySelector('[data-choreo-substance]')\n ?.getBoundingClientRect();\n if (marked) {\n return marked;\n }\n if (el.getAttribute('data-choreo-pack') === 'content') {\n return inkBox(el);\n }\n return undefined;\n};\n\n/** the containing block a `position: absolute` child would be placed against */\nexport const offsetBox = (el: Element): DOMRect =>\n (\n (el as HTMLElement).offsetParent ??\n el.parentElement ??\n el\n ).getBoundingClientRect();\n\nexport const measure = (el: Element): Snapshot => ({\n el: el.getBoundingClientRect(),\n // read with the geometry, while the element is still attached: a removed\n // skin is detached DOM by the time a crossing asks what color it wore\n paint: getComputedStyle(el).backgroundColor,\n parent: offsetBox(el),\n // Keynote matches OBJECTS, not slide frames: an element may declare its\n // visible subject with [data-choreo-substance], or pack=\"content\" (the\n // shrink-wrap / ink, no extra node). Captured with the geometry for\n // the same reason paint is.\n substance: substanceOf(el),\n});\n\n/** the three spaces, from a snapshot and the region's own box */\nexport const boundsOf = (snap: Snapshot, root: DOMRect): Bounds => ({\n context: minus(snap.el, root),\n page: rect(snap.el),\n paint: snap.paint,\n parent: minus(snap.el, snap.parent),\n substance: snap.substance && rect(snap.substance),\n});\n\n/** measure an element straight into bounds — what a beacon needs */\nexport const measureBounds = (el: Element, root: DOMRect): Bounds =>\n boundsOf(measure(el), root);\n"],"names":["defineParticipantArg","el","pack","setAttribute","removeAttribute","rect","r","height","width","x","left","y","top","minus","a","b","inkBox","doc","ownerDocument","undefined","range","createRange","selectNodeContents","getBoundingClientRect","substanceOf","marked","querySelector","getAttribute","offsetBox","offsetParent","parentElement","measure","paint","getComputedStyle","backgroundColor","parent","substance","boundsOf","snap","root","context","page","measureBounds"],"mappings":"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"}
package/dist/path.js ADDED
@@ -0,0 +1,272 @@
1
+ /**
2
+ * THE CAMERA'S PATH — the arithmetic behind `c.Camera3D @through`, and
3
+ * the only place in Choreo that owns a curve rather than a tween.
4
+ *
5
+ * Everything here is a pure function of PROGRESS. That is the whole
6
+ * point: a seek must land the shot exactly where playing there would, so
7
+ * nothing in this file may remember a previous frame. A spline that is
8
+ * sampled rather than integrated can be scrubbed, rendered a frame at a
9
+ * time, and played, and all three agree by construction.
10
+ */
11
+
12
+ /**
13
+ * One cardinal-spline component: Hermite through p1 and p2, tangents off
14
+ * p0 and p3 scaled by `k = (1 - tension) / 2`. Tension 0 is the classic
15
+ * Catmull-Rom — lively, and loose enough to overshoot between waypoints
16
+ * that change direction; 1 collapses the tangents and the path goes
17
+ * piecewise-linear. The default (0.5) is the steady hand: it still crosses
18
+ * every waypoint with continuous velocity, but it does not sway on the way.
19
+ */
20
+ function crVal(p0, p1, p2, p3, u, k) {
21
+ const m1 = k * (p2 - p0);
22
+ const m2 = k * (p3 - p1);
23
+ const u2 = u * u;
24
+ const u3 = u2 * u;
25
+ return (2 * u3 - 3 * u2 + 1) * p1 + (u3 - 2 * u2 + u) * m1 + (-2 * u3 + 3 * u2) * p2 + (u3 - u2) * m2;
26
+ }
27
+
28
+ /**
29
+ * Resolve a `@through` path's control points: the pose in force first, then
30
+ * each waypoint with its omissions carried forward. If ANY point carries a
31
+ * look, every point gets one (the origin, when unstated), so the aim is
32
+ * splined by the same arithmetic as the pose.
33
+ */
34
+ function resolveThrough(from, through) {
35
+ const aimed = !!from.look || through.some(w => w.look);
36
+ const seed = {
37
+ ...from,
38
+ look: aimed ? from.look ?? {
39
+ x: 0,
40
+ y: 0,
41
+ z: 0
42
+ } : undefined
43
+ };
44
+ const pts = [seed];
45
+ let prev = seed;
46
+ for (const w of through) {
47
+ const p = {
48
+ dolly: w.dolly ?? prev.dolly,
49
+ look: aimed ? w.look ?? prev.look : undefined,
50
+ pitch: w.pitch ?? prev.pitch,
51
+ x: w.x ?? prev.x,
52
+ y: w.y ?? prev.y,
53
+ yaw: w.yaw ?? prev.yaw
54
+ };
55
+ pts.push(p);
56
+ prev = p;
57
+ }
58
+ return pts;
59
+ }
60
+
61
+ /** the shot a slot position falls in — points [a, e), window [w0, w1) */
62
+
63
+ /**
64
+ * WHICH SHOT A SLOT POSITION IS IN. A waypoint marked `cut` opens a new
65
+ * one, and the two facts a caller needs about it are the points it may
66
+ * reach for and the slice of the clock it owns — the second so that
67
+ * nothing (a sample, a settle) ever reads across a seam.
68
+ */
69
+ function shotAt(through, points, segs, s) {
70
+ /* the seams, in point indices (+1: the seed sits at 0) */
71
+ let seams;
72
+ for (const [n, w] of through.entries()) {
73
+ if (w.cut) {
74
+ (seams ??= []).push(n + 1);
75
+ }
76
+ }
77
+ let a = 0;
78
+ let e = points;
79
+ let w0 = 0;
80
+ let w1 = segs;
81
+ if (seams) {
82
+ if (seams[0] === 1) {
83
+ /* a leading cut: the seed never plays */
84
+ a = 1;
85
+ seams = seams.slice(1);
86
+ }
87
+ for (const seam of seams) {
88
+ if (s < seam) {
89
+ e = seam;
90
+ w1 = seam;
91
+ break;
92
+ }
93
+ a = seam;
94
+ w0 = seam;
95
+ }
96
+ }
97
+ return {
98
+ a,
99
+ e,
100
+ w0,
101
+ w1
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Sample the spline at overall progress p ∈ [0, 1] (uniform segments).
107
+ *
108
+ * SPLICES (docs/choreo-splices.md): a waypoint marked `cut` starts a new
109
+ * SHOT. Each shot is a clamped spline of its own points — endpoint
110
+ * tangents one-sided, so no shot's velocity crosses a seam — and each
111
+ * shot's window runs from its first slot to the next seam, its points
112
+ * spread uniformly across it. The outgoing shot therefore plays through
113
+ * the seam instant (its motion stretches by one slot rather than
114
+ * parking), the incoming one begins exactly on it, and the sampled pose
115
+ * is a step function at the seam. Waypoints keep their uniform slots, so
116
+ * cues anchored to waypoint moments keep their clock. A cut on the first
117
+ * waypoint drops the pose-in-force seed: the score opens inside its
118
+ * first shot.
119
+ */
120
+ function sampleThrough(from, through, progress, tension) {
121
+ const k = (1 - (tension ?? 0.5)) / 2;
122
+ const pts = resolveThrough(from, through);
123
+ const segs = pts.length - 1;
124
+ const s = Math.min(segs - 1e-9, Math.max(0, progress * segs));
125
+ const {
126
+ a,
127
+ e,
128
+ w0,
129
+ w1
130
+ } = shotAt(through, pts.length, segs, s);
131
+ const m = e - a;
132
+ const lam = m === 1 ? 0 : Math.max(0, Math.min(1 - 1e-9, (s - w0) / (w1 - w0)));
133
+ const ss = lam * (m - 1);
134
+ const i = a + Math.floor(ss);
135
+ const u = m === 1 ? 0 : ss - Math.floor(ss);
136
+ /* clamped to the SHOT: the far side of a seam does not exist here */
137
+ const P = j => pts[Math.max(a, Math.min(e - 1, j))];
138
+ const p0 = P(i - 1);
139
+ const p1 = P(i);
140
+ const p2 = P(i + 1);
141
+ const p3 = P(i + 2);
142
+ const v = key => crVal(p0[key], p1[key], p2[key], p3[key], u, k);
143
+ const out = {
144
+ dolly: v('dolly'),
145
+ pitch: v('pitch'),
146
+ x: v('x'),
147
+ y: v('y'),
148
+ yaw: v('yaw')
149
+ };
150
+ if (p1.look && p2.look) {
151
+ const l = a => crVal((p0.look ?? p1.look)[a], p1.look[a], p2.look[a], (p3.look ?? p2.look)[a], u, k);
152
+ out.look = {
153
+ x: l('x'),
154
+ y: l('y'),
155
+ z: l('z')
156
+ };
157
+ }
158
+ return out;
159
+ }
160
+ /**
161
+ * A SETTLE — the operator's second hand, written as a function of time.
162
+ *
163
+ * A cardinal spline crosses its waypoints with continuous velocity but
164
+ * not continuous acceleration: at every control point the curvature
165
+ * steps, and the eye reads that step as a tick. The usual cure is a
166
+ * spring chasing the pose, which works and costs the two things a
167
+ * seekable engine cannot pay — it remembers the last frame, so a scrub
168
+ * lands somewhere a play would not, and it lags, so the camera is always
169
+ * behind the score by however much smoothing it was given.
170
+ *
171
+ * This is the same cure without either cost: the reported pose is a
172
+ * Hann-weighted average of the SAME spline sampled around the current
173
+ * progress. Averaging a curve is a low-pass filter, and a Hann window
174
+ * kills the acceleration steps outright, so the result is a hand rather
175
+ * than a mechanism. Being centred, it has no lag — it reads slightly
176
+ * ahead of the score exactly as much as it reads behind. Being an
177
+ * average of one pure function, it is still a pure function: play,
178
+ * scrub and frame-by-frame render all report the same pose.
179
+ *
180
+ * TWO EDGES IT MUST NOT CROSS. A cut is a step in the pose on purpose,
181
+ * and an average that straddled it would smear the cut into a very fast
182
+ * pan. The window is therefore TAPERED to the shot the progress is in:
183
+ * near either edge the kernel shrinks to the room it has, reaching zero
184
+ * at the edge itself. So a cut stays a cut, a path still lands exactly
185
+ * on its last waypoint, and the pose handed forward to the next cue is
186
+ * the waypoint rather than an average of one — the filter is an identity
187
+ * at every boundary and a smoother everywhere else.
188
+ *
189
+ * The taper is `tanh`, not a `min`, and the difference is the whole
190
+ * reason it works. A hard `min(width, 2 × room)` has a corner in it
191
+ * where the two branches meet, and a filter whose width has a corner
192
+ * puts one back into what it filters: measured against a path with two
193
+ * right-angle corners in it, the hard taper LOST ground as the window
194
+ * widened (worst jerk 8.7e-4 at a window of 0.175, 2.5e-3 at 0.3) while
195
+ * the smooth one held 8.0e-4 at every width against 4.2e-3 unfiltered.
196
+ * `tanh(x) ≤ x` everywhere, which is what keeps the taps inside the
197
+ * shot; it is smooth, which is what keeps the tick out.
198
+ *
199
+ * `width` is in progress, not seconds: the kinks being filtered are at
200
+ * the waypoints, which are spread uniformly across the clock, so a
201
+ * window measured against the clock is a window measured against the
202
+ * spacing of the things it exists to smooth. The caller converts.
203
+ */
204
+ function settleThrough(from, through, progress, tension, width, taps = 15) {
205
+ const pts = resolveThrough(from, through);
206
+ const segs = pts.length - 1;
207
+ const s = Math.min(segs - 1e-9, Math.max(0, progress * segs));
208
+ const {
209
+ w0,
210
+ w1
211
+ } = shotAt(through, pts.length, segs, s);
212
+ const lo = w0 / segs;
213
+ const hi = w1 / segs;
214
+ const p = Math.min(hi, Math.max(lo, progress));
215
+ /* tapered: the kernel never reaches past the shot it is in */
216
+ const room = Math.min(p - lo, hi - p);
217
+ const w = width > 0 ? width * Math.tanh(2 * room / width) : 0;
218
+ if (!(w > 1e-6) || taps < 3) {
219
+ return sampleThrough(from, through, progress, tension);
220
+ }
221
+ const aimed = !!from.look || through.some(t => t.look);
222
+ let sum = 0;
223
+ let dolly = 0;
224
+ let pitch = 0;
225
+ let x = 0;
226
+ let y = 0;
227
+ let yaw = 0;
228
+ let lx = 0;
229
+ let ly = 0;
230
+ let lz = 0;
231
+ for (let i = 0; i < taps; i += 1) {
232
+ /* the tap fractions avoid 0 and 1, where a Hann window is zero and
233
+ the sample would be carried for nothing */
234
+ const f = (i + 1) / (taps + 1);
235
+ const weight = 0.5 - 0.5 * Math.cos(2 * Math.PI * f);
236
+ const at = p + (f - 0.5) * w;
237
+ const q = sampleThrough(from, through, at, tension);
238
+ sum += weight;
239
+ dolly += weight * q.dolly;
240
+ pitch += weight * q.pitch;
241
+ x += weight * q.x;
242
+ y += weight * q.y;
243
+ /* yaw is averaged as a NUMBER, not as an angle, because the spline
244
+ under it interpolates yaw as a number too — a score unwraps its
245
+ own turns (`Camera3DStep.through`) and the filter must agree with
246
+ the curve it is filtering rather than second-guess it */
247
+ yaw += weight * q.yaw;
248
+ if (aimed && q.look) {
249
+ lx += weight * q.look.x;
250
+ ly += weight * q.look.y;
251
+ lz += weight * q.look.z;
252
+ }
253
+ }
254
+ const out = {
255
+ dolly: dolly / sum,
256
+ pitch: pitch / sum,
257
+ x: x / sum,
258
+ y: y / sum,
259
+ yaw: yaw / sum
260
+ };
261
+ if (aimed) {
262
+ out.look = {
263
+ x: lx / sum,
264
+ y: ly / sum,
265
+ z: lz / sum
266
+ };
267
+ }
268
+ return out;
269
+ }
270
+
271
+ export { sampleThrough, settleThrough };
272
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sources":["../src/path.ts"],"sourcesContent":["/**\n * THE CAMERA'S PATH — the arithmetic behind `c.Camera3D @through`, and\n * the only place in Choreo that owns a curve rather than a tween.\n *\n * Everything here is a pure function of PROGRESS. That is the whole\n * point: a seek must land the shot exactly where playing there would, so\n * nothing in this file may remember a previous frame. A spline that is\n * sampled rather than integrated can be scrubbed, rendered a frame at a\n * time, and played, and all three agree by construction.\n */\nimport type { Camera3DState, Camera3DWaypoint } from './types.ts';\n\n/**\n * One cardinal-spline component: Hermite through p1 and p2, tangents off\n * p0 and p3 scaled by `k = (1 - tension) / 2`. Tension 0 is the classic\n * Catmull-Rom — lively, and loose enough to overshoot between waypoints\n * that change direction; 1 collapses the tangents and the path goes\n * piecewise-linear. The default (0.5) is the steady hand: it still crosses\n * every waypoint with continuous velocity, but it does not sway on the way.\n */\nfunction crVal(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n u: number,\n k: number,\n) {\n const m1 = k * (p2 - p0);\n const m2 = k * (p3 - p1);\n const u2 = u * u;\n const u3 = u2 * u;\n return (\n (2 * u3 - 3 * u2 + 1) * p1 +\n (u3 - 2 * u2 + u) * m1 +\n (-2 * u3 + 3 * u2) * p2 +\n (u3 - u2) * m2\n );\n}\n\n/**\n * Resolve a `@through` path's control points: the pose in force first, then\n * each waypoint with its omissions carried forward. If ANY point carries a\n * look, every point gets one (the origin, when unstated), so the aim is\n * splined by the same arithmetic as the pose.\n */\nfunction resolveThrough(\n from: Camera3DState,\n through: Camera3DWaypoint[],\n): Camera3DState[] {\n const aimed = !!from.look || through.some((w) => w.look);\n const seed: Camera3DState = {\n ...from,\n look: aimed ? (from.look ?? { x: 0, y: 0, z: 0 }) : undefined,\n };\n const pts = [seed];\n let prev = seed;\n for (const w of through) {\n const p: Camera3DState = {\n dolly: w.dolly ?? prev.dolly,\n look: aimed ? (w.look ?? prev.look) : undefined,\n pitch: w.pitch ?? prev.pitch,\n x: w.x ?? prev.x,\n y: w.y ?? prev.y,\n yaw: w.yaw ?? prev.yaw,\n };\n pts.push(p);\n prev = p;\n }\n return pts;\n}\n\n/** the shot a slot position falls in — points [a, e), window [w0, w1) */\ninterface Shot {\n /** first point of the shot */\n a: number;\n /** one past its last point */\n e: number;\n /** the shot's window, in slot units */\n w0: number;\n w1: number;\n}\n\n/**\n * WHICH SHOT A SLOT POSITION IS IN. A waypoint marked `cut` opens a new\n * one, and the two facts a caller needs about it are the points it may\n * reach for and the slice of the clock it owns — the second so that\n * nothing (a sample, a settle) ever reads across a seam.\n */\nfunction shotAt(\n through: Camera3DWaypoint[],\n points: number,\n segs: number,\n s: number,\n): Shot {\n /* the seams, in point indices (+1: the seed sits at 0) */\n let seams: number[] | undefined;\n for (const [n, w] of through.entries()) {\n if (w.cut) {\n (seams ??= []).push(n + 1);\n }\n }\n let a = 0;\n let e = points;\n let w0 = 0;\n let w1 = segs;\n if (seams) {\n if (seams[0] === 1) {\n /* a leading cut: the seed never plays */\n a = 1;\n seams = seams.slice(1);\n }\n for (const seam of seams) {\n if (s < seam) {\n e = seam;\n w1 = seam;\n break;\n }\n a = seam;\n w0 = seam;\n }\n }\n return { a, e, w0, w1 };\n}\n\n/**\n * Sample the spline at overall progress p ∈ [0, 1] (uniform segments).\n *\n * SPLICES (docs/choreo-splices.md): a waypoint marked `cut` starts a new\n * SHOT. Each shot is a clamped spline of its own points — endpoint\n * tangents one-sided, so no shot's velocity crosses a seam — and each\n * shot's window runs from its first slot to the next seam, its points\n * spread uniformly across it. The outgoing shot therefore plays through\n * the seam instant (its motion stretches by one slot rather than\n * parking), the incoming one begins exactly on it, and the sampled pose\n * is a step function at the seam. Waypoints keep their uniform slots, so\n * cues anchored to waypoint moments keep their clock. A cut on the first\n * waypoint drops the pose-in-force seed: the score opens inside its\n * first shot.\n */\nexport function sampleThrough(\n from: Camera3DState,\n through: Camera3DWaypoint[],\n progress: number,\n tension?: number,\n): Camera3DState {\n const k = (1 - (tension ?? 0.5)) / 2;\n const pts = resolveThrough(from, through);\n const segs = pts.length - 1;\n const s = Math.min(segs - 1e-9, Math.max(0, progress * segs));\n const { a, e, w0, w1 } = shotAt(through, pts.length, segs, s);\n const m = e - a;\n const lam =\n m === 1 ? 0 : Math.max(0, Math.min(1 - 1e-9, (s - w0) / (w1 - w0)));\n const ss = lam * (m - 1);\n const i = a + Math.floor(ss);\n const u = m === 1 ? 0 : ss - Math.floor(ss);\n /* clamped to the SHOT: the far side of a seam does not exist here */\n const P = (j: number) => pts[Math.max(a, Math.min(e - 1, j))]!;\n const p0 = P(i - 1);\n const p1 = P(i);\n const p2 = P(i + 1);\n const p3 = P(i + 2);\n const v = (key: 'dolly' | 'pitch' | 'x' | 'y' | 'yaw') =>\n crVal(p0[key], p1[key], p2[key], p3[key], u, k);\n const out: Camera3DState = {\n dolly: v('dolly'),\n pitch: v('pitch'),\n x: v('x'),\n y: v('y'),\n yaw: v('yaw'),\n };\n if (p1.look && p2.look) {\n const l = (a: 'x' | 'y' | 'z') =>\n crVal(\n (p0.look ?? p1.look)![a],\n p1.look![a],\n p2.look![a],\n (p3.look ?? p2.look)![a],\n u,\n k,\n );\n out.look = { x: l('x'), y: l('y'), z: l('z') };\n }\n return out;\n}\n/**\n * A SETTLE — the operator's second hand, written as a function of time.\n *\n * A cardinal spline crosses its waypoints with continuous velocity but\n * not continuous acceleration: at every control point the curvature\n * steps, and the eye reads that step as a tick. The usual cure is a\n * spring chasing the pose, which works and costs the two things a\n * seekable engine cannot pay — it remembers the last frame, so a scrub\n * lands somewhere a play would not, and it lags, so the camera is always\n * behind the score by however much smoothing it was given.\n *\n * This is the same cure without either cost: the reported pose is a\n * Hann-weighted average of the SAME spline sampled around the current\n * progress. Averaging a curve is a low-pass filter, and a Hann window\n * kills the acceleration steps outright, so the result is a hand rather\n * than a mechanism. Being centred, it has no lag — it reads slightly\n * ahead of the score exactly as much as it reads behind. Being an\n * average of one pure function, it is still a pure function: play,\n * scrub and frame-by-frame render all report the same pose.\n *\n * TWO EDGES IT MUST NOT CROSS. A cut is a step in the pose on purpose,\n * and an average that straddled it would smear the cut into a very fast\n * pan. The window is therefore TAPERED to the shot the progress is in:\n * near either edge the kernel shrinks to the room it has, reaching zero\n * at the edge itself. So a cut stays a cut, a path still lands exactly\n * on its last waypoint, and the pose handed forward to the next cue is\n * the waypoint rather than an average of one — the filter is an identity\n * at every boundary and a smoother everywhere else.\n *\n * The taper is `tanh`, not a `min`, and the difference is the whole\n * reason it works. A hard `min(width, 2 × room)` has a corner in it\n * where the two branches meet, and a filter whose width has a corner\n * puts one back into what it filters: measured against a path with two\n * right-angle corners in it, the hard taper LOST ground as the window\n * widened (worst jerk 8.7e-4 at a window of 0.175, 2.5e-3 at 0.3) while\n * the smooth one held 8.0e-4 at every width against 4.2e-3 unfiltered.\n * `tanh(x) ≤ x` everywhere, which is what keeps the taps inside the\n * shot; it is smooth, which is what keeps the tick out.\n *\n * `width` is in progress, not seconds: the kinks being filtered are at\n * the waypoints, which are spread uniformly across the clock, so a\n * window measured against the clock is a window measured against the\n * spacing of the things it exists to smooth. The caller converts.\n */\nexport function settleThrough(\n from: Camera3DState,\n through: Camera3DWaypoint[],\n progress: number,\n tension: number | undefined,\n width: number,\n taps = 15,\n): Camera3DState {\n const pts = resolveThrough(from, through);\n const segs = pts.length - 1;\n const s = Math.min(segs - 1e-9, Math.max(0, progress * segs));\n const { w0, w1 } = shotAt(through, pts.length, segs, s);\n const lo = w0 / segs;\n const hi = w1 / segs;\n const p = Math.min(hi, Math.max(lo, progress));\n /* tapered: the kernel never reaches past the shot it is in */\n const room = Math.min(p - lo, hi - p);\n const w = width > 0 ? width * Math.tanh((2 * room) / width) : 0;\n if (!(w > 1e-6) || taps < 3) {\n return sampleThrough(from, through, progress, tension);\n }\n const aimed = !!from.look || through.some((t) => t.look);\n let sum = 0;\n let dolly = 0;\n let pitch = 0;\n let x = 0;\n let y = 0;\n let yaw = 0;\n let lx = 0;\n let ly = 0;\n let lz = 0;\n for (let i = 0; i < taps; i += 1) {\n /* the tap fractions avoid 0 and 1, where a Hann window is zero and\n the sample would be carried for nothing */\n const f = (i + 1) / (taps + 1);\n const weight = 0.5 - 0.5 * Math.cos(2 * Math.PI * f);\n const at = p + (f - 0.5) * w;\n const q = sampleThrough(from, through, at, tension);\n sum += weight;\n dolly += weight * q.dolly;\n pitch += weight * q.pitch;\n x += weight * q.x;\n y += weight * q.y;\n /* yaw is averaged as a NUMBER, not as an angle, because the spline\n under it interpolates yaw as a number too — a score unwraps its\n own turns (`Camera3DStep.through`) and the filter must agree with\n the curve it is filtering rather than second-guess it */\n yaw += weight * q.yaw;\n if (aimed && q.look) {\n lx += weight * q.look.x;\n ly += weight * q.look.y;\n lz += weight * q.look.z;\n }\n }\n const out: Camera3DState = {\n dolly: dolly / sum,\n pitch: pitch / sum,\n x: x / sum,\n y: y / sum,\n yaw: yaw / sum,\n };\n if (aimed) {\n out.look = { x: lx / sum, y: ly / sum, z: lz / sum };\n }\n return out;\n}\n"],"names":["crVal","p0","p1","p2","p3","u","k","m1","m2","u2","u3","resolveThrough","from","through","aimed","look","some","w","seed","x","y","z","undefined","pts","prev","p","dolly","pitch","yaw","push","shotAt","points","segs","s","seams","n","entries","cut","a","e","w0","w1","slice","seam","sampleThrough","progress","tension","length","Math","min","max","m","lam","ss","i","floor","P","j","v","key","out","l","settleThrough","width","taps","lo","hi","room","tanh","t","sum","lx","ly","lz","f","weight","cos","PI","at","q"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { setParticipantHost } from 'glimmer-motion';
2
+
3
+ /**
4
+ * A region is a participant host (glimmer-motion/participant): its root
5
+ * renders `data-motion-host` beside `data-choreo`, and registering a region
6
+ * here registers it as that element's participant host too, so the {{motion}}
7
+ * elements inside join it. The lookups below are Choreo's own, for code that
8
+ * already speaks in regions.
9
+ */
10
+
11
+ // The `{{motion}}` args a region adds, typed here because the package's root
12
+ // declarations re-export this module; measure.ts, which applies `pack`, is
13
+ // reached only at runtime, so an augmentation there would not ship.
14
+
15
+ /** anything that can put a node on a region's timeline — a step component, or a lane from outside */
16
+
17
+ const hosts = new WeakMap();
18
+ /** regions rendered with `@id`, so an attachment can name one without a DOM query per frame */
19
+ const hostsById = new Map();
20
+ function setChoreoHost(el, host) {
21
+ const id = el.getAttribute('data-choreo');
22
+ if (host) {
23
+ hosts.set(el, host);
24
+ setParticipantHost(el, host);
25
+ if (id) {
26
+ hostsById.set(id, host);
27
+ }
28
+ } else {
29
+ // a region re-keyed under the same id may already have registered its
30
+ // successor: only the entry that is THIS host's comes off
31
+ const mine = hosts.get(el);
32
+ hosts.delete(el);
33
+ setParticipantHost(el, undefined);
34
+ if (id && mine && hostsById.get(id) === mine) {
35
+ hostsById.delete(id);
36
+ }
37
+ }
38
+ }
39
+ function closestChoreo(el) {
40
+ const root = el.parentElement?.closest('[data-choreo]');
41
+ return root ? hosts.get(root) : undefined;
42
+ }
43
+
44
+ /** the host of a region element itself (not an ancestor's) */
45
+ function choreoHostAt(el) {
46
+ return hosts.get(el);
47
+ }
48
+
49
+ /** the host of the region rendered with `@id` — a lane's way of naming the region it plays in */
50
+ function choreoHostById(id) {
51
+ return hostsById.get(id);
52
+ }
53
+
54
+ export { choreoHostAt, choreoHostById, closestChoreo, setChoreoHost };
55
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sources":["../src/registry.ts"],"sourcesContent":["/**\n * A region is a participant host (glimmer-motion/participant): its root\n * renders `data-motion-host` beside `data-choreo`, and registering a region\n * here registers it as that element's participant host too, so the {{motion}}\n * elements inside join it. The lookups below are Choreo's own, for code that\n * already speaks in regions.\n */\nimport { type ParticipantHost, setParticipantHost } from 'glimmer-motion';\n\nimport type { ChoreoRun } from './run.ts';\nimport type { ChoreoNode, TimelineNode } from './types.ts';\n\n// The `{{motion}}` args a region adds, typed here because the package's root\n// declarations re-export this module; measure.ts, which applies `pack`, is\n// reached only at runtime, so an augmentation there would not ship.\ndeclare module 'glimmer-motion/participant' {\n interface ParticipantArgs {\n /**\n * How Choreo measures this element for a shape-matched flight.\n * `'box'` (default) is the layout border box — right for plates, cards,\n * stages. `'content'` is the shrink-wrap (the ink): a full-bleed title\n * still matches as a word. Written as `data-choreo-pack`; an explicit\n * `[data-choreo-substance]` descendant still wins.\n */\n pack?: 'box' | 'content';\n }\n}\n\n/** anything that can put a node on a region's timeline — a step component, or a lane from outside */\nexport interface ChoreoProvider {\n node(): TimelineNode;\n}\n\nexport interface ChoreoHost extends ParticipantHost {\n /** a destroyed participant asks whether the region still needs its element; true → the region unmounts it later */\n claim(node: ChoreoNode): boolean;\n /**\n * SPIKE (Lane): a provider from OUTSIDE the region's markup — a parent's\n * lane addressed at this region — whose node joins the tree the region\n * collects on every pass, after its own steps. Returns the remover.\n */\n contribute(provider: ChoreoProvider): () => void;\n /** the run the region is playing or holding, for an attachment to drive; null between runs */\n currentRun(): ChoreoRun | null;\n register(node: ChoreoNode): () => void;\n}\n\nconst hosts = new WeakMap<Element, ChoreoHost>();\n/** regions rendered with `@id`, so an attachment can name one without a DOM query per frame */\nconst hostsById = new Map<string, ChoreoHost>();\n\nexport function setChoreoHost(el: Element, host: ChoreoHost | undefined) {\n const id = el.getAttribute('data-choreo');\n if (host) {\n hosts.set(el, host);\n setParticipantHost(el, host);\n if (id) {\n hostsById.set(id, host);\n }\n } else {\n // a region re-keyed under the same id may already have registered its\n // successor: only the entry that is THIS host's comes off\n const mine = hosts.get(el);\n hosts.delete(el);\n setParticipantHost(el, undefined);\n if (id && mine && hostsById.get(id) === mine) {\n hostsById.delete(id);\n }\n }\n}\n\nexport function closestChoreo(el: Element): ChoreoHost | undefined {\n const root = el.parentElement?.closest('[data-choreo]');\n return root ? hosts.get(root) : undefined;\n}\n\n/** the host of a region element itself (not an ancestor's) */\nexport function choreoHostAt(el: Element): ChoreoHost | undefined {\n return hosts.get(el);\n}\n\n/** the host of the region rendered with `@id` — a lane's way of naming the region it plays in */\nexport function choreoHostById(id: string): ChoreoHost | undefined {\n return hostsById.get(id);\n}\n"],"names":["hosts","WeakMap","hostsById","Map","setChoreoHost","el","host","id","getAttribute","set","setParticipantHost","mine","get","delete","undefined","closestChoreo","root","parentElement","closest","choreoHostAt","choreoHostById"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;;AAcA;;AAmBA,MAAMA,KAAK,GAAG,IAAIC,OAAO,EAAuB;AAChD;AACA,MAAMC,SAAS,GAAG,IAAIC,GAAG,EAAsB;AAExC,SAASC,aAAaA,CAACC,EAAW,EAAEC,IAA4B,EAAE;AACvE,EAAA,MAAMC,EAAE,GAAGF,EAAE,CAACG,YAAY,CAAC,aAAa,CAAC;AACzC,EAAA,IAAIF,IAAI,EAAE;AACRN,IAAAA,KAAK,CAACS,GAAG,CAACJ,EAAE,EAAEC,IAAI,CAAC;AACnBI,IAAAA,kBAAkB,CAACL,EAAE,EAAEC,IAAI,CAAC;AAC5B,IAAA,IAAIC,EAAE,EAAE;AACNL,MAAAA,SAAS,CAACO,GAAG,CAACF,EAAE,EAAED,IAAI,CAAC;AACzB,IAAA;AACF,EAAA,CAAC,MAAM;AACL;AACA;AACA,IAAA,MAAMK,IAAI,GAAGX,KAAK,CAACY,GAAG,CAACP,EAAE,CAAC;AAC1BL,IAAAA,KAAK,CAACa,MAAM,CAACR,EAAE,CAAC;AAChBK,IAAAA,kBAAkB,CAACL,EAAE,EAAES,SAAS,CAAC;AACjC,IAAA,IAAIP,EAAE,IAAII,IAAI,IAAIT,SAAS,CAACU,GAAG,CAACL,EAAE,CAAC,KAAKI,IAAI,EAAE;AAC5CT,MAAAA,SAAS,CAACW,MAAM,CAACN,EAAE,CAAC;AACtB,IAAA;AACF,EAAA;AACF;AAEO,SAASQ,aAAaA,CAACV,EAAW,EAA0B;EACjE,MAAMW,IAAI,GAAGX,EAAE,CAACY,aAAa,EAAEC,OAAO,CAAC,eAAe,CAAC;EACvD,OAAOF,IAAI,GAAGhB,KAAK,CAACY,GAAG,CAACI,IAAI,CAAC,GAAGF,SAAS;AAC3C;;AAEA;AACO,SAASK,YAAYA,CAACd,EAAW,EAA0B;AAChE,EAAA,OAAOL,KAAK,CAACY,GAAG,CAACP,EAAE,CAAC;AACtB;;AAEA;AACO,SAASe,cAAcA,CAACb,EAAU,EAA0B;AACjE,EAAA,OAAOL,SAAS,CAACU,GAAG,CAACL,EAAE,CAAC;AAC1B;;;;"}