@motionscript/code 0.1.0 → 0.1.1-alpha.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 (65) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -2
  3. package/dist/browser/index.js +8 -8
  4. package/dist/browser/index.js.map +3 -3
  5. package/dist/browser/manifest.json +1 -1
  6. package/dist/code-range.d.ts +7 -47
  7. package/dist/code-range.d.ts.map +1 -1
  8. package/dist/code-range.js +8 -45
  9. package/dist/code-range.js.map +1 -1
  10. package/dist/diff.d.ts +5 -44
  11. package/dist/diff.d.ts.map +1 -1
  12. package/dist/diff.js +22 -69
  13. package/dist/diff.js.map +1 -1
  14. package/dist/engine.d.ts.map +1 -1
  15. package/dist/engine.js +3 -7
  16. package/dist/engine.js.map +1 -1
  17. package/dist/highlight.d.ts +13 -31
  18. package/dist/highlight.d.ts.map +1 -1
  19. package/dist/highlight.js +25 -87
  20. package/dist/highlight.js.map +1 -1
  21. package/dist/layout.d.ts +7 -26
  22. package/dist/layout.d.ts.map +1 -1
  23. package/dist/layout.js +5 -20
  24. package/dist/layout.js.map +1 -1
  25. package/dist/measure-cache.d.ts +5 -27
  26. package/dist/measure-cache.d.ts.map +1 -1
  27. package/dist/measure-cache.js +0 -0
  28. package/dist/measure-cache.js.map +1 -1
  29. package/dist/node.d.ts +4 -11
  30. package/dist/node.d.ts.map +1 -1
  31. package/dist/node.js +18 -41
  32. package/dist/node.js.map +1 -1
  33. package/dist/nodes.d.ts +0 -12
  34. package/dist/nodes.d.ts.map +1 -1
  35. package/dist/nodes.js +0 -12
  36. package/dist/nodes.js.map +1 -1
  37. package/dist/render.d.ts +5 -14
  38. package/dist/render.d.ts.map +1 -1
  39. package/dist/render.js +13 -54
  40. package/dist/render.js.map +1 -1
  41. package/dist/style.d.ts +6 -61
  42. package/dist/style.d.ts.map +1 -1
  43. package/dist/style.js +4 -40
  44. package/dist/style.js.map +1 -1
  45. package/dist/tokens.d.ts +1 -10
  46. package/dist/tokens.d.ts.map +1 -1
  47. package/dist/tokens.js +3 -38
  48. package/dist/tokens.js.map +1 -1
  49. package/dist/transitions.d.ts +16 -68
  50. package/dist/transitions.d.ts.map +1 -1
  51. package/dist/transitions.js +5 -17
  52. package/dist/transitions.js.map +1 -1
  53. package/package.json +4 -4
  54. package/src/code-range.ts +9 -53
  55. package/src/diff.ts +26 -92
  56. package/src/engine.ts +3 -7
  57. package/src/highlight.ts +25 -98
  58. package/src/layout.ts +10 -35
  59. package/src/measure-cache.ts +0 -0
  60. package/src/node.ts +21 -57
  61. package/src/nodes.ts +0 -12
  62. package/src/render.ts +17 -61
  63. package/src/style.ts +9 -64
  64. package/src/tokens.ts +4 -46
  65. package/src/transitions.ts +16 -72
package/dist/tokens.js CHANGED
@@ -1,7 +1,4 @@
1
1
  import { canHighlight, highlightToTokens, normalizeSource, resolveTheme } from "./highlight.js";
2
- // Module-global, monotonic id counters shared across all Code nodes — ids only
3
- // need to be unique within a node, but a global counter is the simplest way to
4
- // guarantee that and never collides.
5
2
  let nextTokenId = 1;
6
3
  let nextLineId = 1;
7
4
  export function makeIdToken(content, color) {
@@ -11,44 +8,12 @@ export function makeIdLine(tokens) {
11
8
  return { id: nextLineId++, tokens };
12
9
  }
13
10
  export function tokenizeCodeToIdLines(code, language, theme) {
14
- // Until the language parser has loaded (it streams in via the asset loader),
15
- // fall back to flat, uncolored lines instead of throwing on an unloaded
16
- // grammar. The node re-tokenizes once the load completes.
17
11
  code = normalizeSource(code);
18
- if (!canHighlight(language, theme)) {
19
- // Resolved for its warning: an unknown theme is reported whether or not
20
- // the grammar has landed yet.
21
- resolveTheme(theme);
12
+ if (!canHighlight(language)) {
13
+ // Flat lines until the grammar loads; the node re-tokenizes then.
14
+ resolveTheme(theme); // so an unknown theme still warns before the grammar lands
22
15
  return code.split('\n').map(line => makeIdLine([makeIdToken(line, '#d1d5db')]));
23
16
  }
24
17
  return highlightToTokens(code, language, theme).map(line => makeIdLine(line.map(tok => makeIdToken(tok.content, tok.color))));
25
18
  }
26
- /**
27
- * Split `tokens` at character offset `col` (relative to the joined content of
28
- * the tokens). If `col` falls inside a token, that token is split into two new
29
- * tokens with fresh ids (the original id is discarded — it didn't exist as a
30
- * single unit on either side of the cut).
31
- */
32
- export function splitTokensAt(tokens, col) {
33
- const before = [];
34
- const after = [];
35
- let off = 0;
36
- for (const tok of tokens) {
37
- const tStart = off;
38
- const tEnd = off + tok.content.length;
39
- if (tEnd <= col) {
40
- before.push(tok);
41
- }
42
- else if (tStart >= col) {
43
- after.push(tok);
44
- }
45
- else {
46
- const cut = col - tStart;
47
- before.push(makeIdToken(tok.content.slice(0, cut), tok.color));
48
- after.push(makeIdToken(tok.content.slice(cut), tok.color));
49
- }
50
- off = tEnd;
51
- }
52
- return { before, after };
53
- }
54
19
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoB7F,+EAA+E;AAC/E,+EAA+E;AAC/E,qCAAqC;AACrC,IAAI,WAAW,GAAG,CAAC,CAAC;AACpB,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAc;IACvD,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAiB;IACxC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAgB;IAClF,6EAA6E;IAC7E,wEAAwE;IACxE,0DAA0D;IAC1D,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;QACjC,wEAAwE;QACxE,8BAA8B;QAC9B,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACvD,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CACnE,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CACzB,MAAiB,EACjB,GAAW;IAEX,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC;QACnB,MAAM,IAAI,GAAG,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QACtC,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACJ,MAAM,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,GAAG,GAAG,IAAI,CAAC;IACf,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC","sourcesContent":["import { canHighlight, highlightToTokens, normalizeSource, resolveTheme } from \"./highlight\";\nimport { CodeTheme } from \"./style\";\n\n// IdToken is our internal wrapper around a highlighter token that gives every\n// token a stable identity across line insertions/removals. All animation state\n// is keyed by `id`, so concurrent edits at different positions don't clobber\n// each other while one is mid-animation.\nexport interface IdToken {\n id: number;\n content: string;\n color?: string;\n}\n\n// IdLine wraps a row of tokens with its own stable id, so per-line animations\n// (heightScale during insert/remove) survive line insertions/removals.\nexport interface IdLine {\n id: number;\n tokens: IdToken[];\n}\n\n// Module-global, monotonic id counters shared across all Code nodes — ids only\n// need to be unique within a node, but a global counter is the simplest way to\n// guarantee that and never collides.\nlet nextTokenId = 1;\nlet nextLineId = 1;\n\nexport function makeIdToken(content: string, color?: string): IdToken {\n return { id: nextTokenId++, content, color };\n}\n\nexport function makeIdLine(tokens: IdToken[]): IdLine {\n return { id: nextLineId++, tokens };\n}\n\nexport function tokenizeCodeToIdLines(code: string, language: string, theme: CodeTheme): IdLine[] {\n // Until the language parser has loaded (it streams in via the asset loader),\n // fall back to flat, uncolored lines instead of throwing on an unloaded\n // grammar. The node re-tokenizes once the load completes.\n code = normalizeSource(code);\n if (!canHighlight(language, theme)) {\n // Resolved for its warning: an unknown theme is reported whether or not\n // the grammar has landed yet.\n resolveTheme(theme);\n return code.split('\\n').map(line => makeIdLine([makeIdToken(line, '#d1d5db')]));\n }\n return highlightToTokens(code, language, theme).map(line =>\n makeIdLine(line.map(tok => makeIdToken(tok.content, tok.color))),\n );\n}\n\n/**\n * Split `tokens` at character offset `col` (relative to the joined content of\n * the tokens). If `col` falls inside a token, that token is split into two new\n * tokens with fresh ids (the original id is discarded — it didn't exist as a\n * single unit on either side of the cut).\n */\nexport function splitTokensAt(\n tokens: IdToken[],\n col: number,\n): { before: IdToken[]; after: IdToken[] } {\n const before: IdToken[] = [];\n const after: IdToken[] = [];\n let off = 0;\n for (const tok of tokens) {\n const tStart = off;\n const tEnd = off + tok.content.length;\n if (tEnd <= col) {\n before.push(tok);\n } else if (tStart >= col) {\n after.push(tok);\n } else {\n const cut = col - tStart;\n before.push(makeIdToken(tok.content.slice(0, cut), tok.color));\n after.push(makeIdToken(tok.content.slice(cut), tok.color));\n }\n off = tEnd;\n }\n return { before, after };\n}\n"]}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAe7F,IAAI,WAAW,GAAG,CAAC,CAAC;AACpB,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAc;IACvD,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAiB;IACxC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAgB;IAClF,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,kEAAkE;QAClE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,2DAA2D;QAChF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACvD,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CACnE,CAAC;AACN,CAAC","sourcesContent":["import { canHighlight, highlightToTokens, normalizeSource, resolveTheme } from \"./highlight\";\nimport { CodeTheme } from \"./style\";\n\n/** A highlighter token with a stable identity: animation state is keyed by `id`, so concurrent edits at different positions don't clobber each other. */\nexport interface IdToken {\n id: number;\n content: string;\n color?: string;\n}\n\nexport interface IdLine {\n id: number;\n tokens: IdToken[];\n}\n\nlet nextTokenId = 1;\nlet nextLineId = 1;\n\nexport function makeIdToken(content: string, color?: string): IdToken {\n return { id: nextTokenId++, content, color };\n}\n\nexport function makeIdLine(tokens: IdToken[]): IdLine {\n return { id: nextLineId++, tokens };\n}\n\nexport function tokenizeCodeToIdLines(code: string, language: string, theme: CodeTheme): IdLine[] {\n code = normalizeSource(code);\n if (!canHighlight(language)) {\n // Flat lines until the grammar loads; the node re-tokenizes then.\n resolveTheme(theme); // so an unknown theme still warns before the grammar lands\n return code.split('\\n').map(line => makeIdLine([makeIdToken(line, '#d1d5db')]));\n }\n return highlightToTokens(code, language, theme).map(line =>\n makeIdLine(line.map(tok => makeIdToken(tok.content, tok.color))),\n );\n}\n"]}
@@ -1,71 +1,40 @@
1
1
  import type { NormalizedColor } from "@motionscript/sdk";
2
2
  import type { CodeLayout } from "./layout.js";
3
3
  import { IdLine } from "./tokens.js";
4
+ /** Keyed by token id, not (line, index), so a concurrent edit that shifts indices doesn't clobber it. */
4
5
  export interface AnimToken {
5
6
  id: number;
6
7
  opacity: number[];
7
8
  opacityKeys: number[];
8
9
  }
9
- /**
10
- * A highlight cross-fade: opacity, and nothing else.
11
- *
12
- * Structure is untouched, so several of these stack happily and a listing held
13
- * under a highlight still hits the static-layout cache.
14
- */
10
+ /** A highlight cross-fade: opacity only, so several stack and a highlighted listing still hits the static-layout cache. */
15
11
  export interface DimTransition {
16
12
  kind: "dim";
17
13
  tokens: AnimToken[];
18
14
  progress: number;
19
15
  }
20
- /**
21
- * When each part of a structural edit happens, in normalized transition time.
22
- *
23
- * The order is what an edit *looks like* being made: what is going away leaves
24
- * first, what stays reflows into its new place, and only then does what is new
25
- * appear. Running them together is what made a replacement look like a
26
- * cross-dissolve of two unrelated listings.
27
- */
16
+ /** When each part of an edit happens, in normalized time: out, then move, then in. Run together, a replacement reads as a cross-dissolve of two listings. */
28
17
  export interface Phases {
29
- /** Removed tokens fade out. Null when the edit removes nothing. */
18
+ /** Null when the edit removes nothing. */
30
19
  out: [number, number] | null;
31
- /** Surviving tokens travel, and the block resizes. Always present. */
20
+ /** Surviving tokens travel and the block resizes. */
32
21
  move: [number, number];
33
- /** Added tokens fade (and slide) in. Null when the edit adds nothing. */
22
+ /** Null when the edit adds nothing. */
34
23
  in: [number, number] | null;
35
24
  }
36
- /**
37
- * One edit in flight: the structure before, the structure after, and who is
38
- * doing what.
39
- *
40
- * Both endpoints are fixed for the transition's whole life, which is why the
41
- * two resolved layouts hang off it — they are computed once and reused for
42
- * every frame the edit spans, rather than rebuilt per frame from a structure
43
- * that is being mutated underneath.
44
- */
25
+ /** One edit in flight. Both endpoints are fixed, so the two layouts are computed once and reused for every frame. */
45
26
  export interface StructuralTransition {
46
27
  kind: "structural";
47
28
  from: IdLine[];
48
29
  to: IdLine[];
49
30
  removedIds: Set<number>;
50
31
  addedIds: Set<number>;
51
- /**
52
- * Ids of rows in `to` that did not exist before, and so enter as a whole
53
- * row rather than as tokens spliced into one.
54
- *
55
- * They enter by fading in place, and that is the *only* thing they do. An
56
- * earlier version slid each one in from the edge the block had just grown
57
- * along — appended rows rising from below, prepended ones descending, ones
58
- * spliced into the middle arriving from the left. Every one of those is a
59
- * claim about where the line came from, and a listing has no such story to
60
- * tell: text is written where it belongs, it does not travel there. Sliding
61
- * also puts the row under the neighbours it is meant to sit between for the
62
- * length of its entrance, which reads as the listing redrawing itself.
63
- */
32
+ /** Rows of `to` that enter as a whole row. They only fade in place: text is written where it belongs, and sliding one in puts it under its neighbours. */
64
33
  enteringLineIds: Set<number>;
65
34
  fromColorById: Map<number, string | undefined>;
66
35
  phases: Phases;
67
36
  progress: number;
68
- /** @internal Layout cache — see {@link StructuralTransition}. */
37
+ /** @internal */
69
38
  layoutKey: string | null;
70
39
  fromLayout: CodeLayout | null;
71
40
  toLayout: CodeLayout | null;
@@ -77,10 +46,7 @@ export interface RowBand {
77
46
  color: NormalizedColor;
78
47
  radius: number;
79
48
  }
80
- /**
81
- * One set of row bands becoming another. Bands at the same position in both
82
- * lists travel and recolour; one with no counterpart fades.
83
- */
49
+ /** Bands at the same position in both lists travel and recolour; one with no counterpart fades. */
84
50
  export interface RowsTransition {
85
51
  kind: "rows";
86
52
  from: RowBand[];
@@ -88,18 +54,12 @@ export interface RowsTransition {
88
54
  progress: number;
89
55
  }
90
56
  export type CodeTransition = DimTransition | StructuralTransition | RowsTransition;
91
- /**
92
- * Decides *when* (in normalized transition time) each part of a structural
93
- * edit happens, given only whether the edit removes/adds anything.
94
- * `phasesFor` below is the default implementation (exported as
95
- * `defaultCodePhases`) — pass a `phaseStrategy` of this shape to `<Code>` to
96
- * replace it with your own timing.
97
- */
57
+ /** Decides the {@link Phases} from whether the edit removes or adds anything; `defaultCodePhases` is the default. */
98
58
  export type CodePhaseStrategy = (hasRemoved: boolean, hasAdded: boolean) => Phases;
99
59
  export interface TokenState {
100
60
  opacity: number;
101
61
  }
102
- /** Sample a piecewise-linear curve (keys → values) at progress `p` in [0, 1]. */
62
+ /** Samples a piecewise-linear curve at progress `p`. */
103
63
  export declare function sampleCurve(keys: number[], values: number[], p: number): number;
104
64
  /** Progress *within* a phase window, clamped to [0, 1]. A null window is complete. */
105
65
  export declare function windowProgress(w: [number, number] | null, p: number): number;
@@ -109,25 +69,13 @@ export declare function makeAnim(id: number, curve: {
109
69
  keys: number[];
110
70
  values: number[];
111
71
  }): AnimToken;
112
- /**
113
- * The phase schedule for an edit, shaped by what the edit actually contains.
114
- *
115
- * A fixed schedule wastes time it does not need: a pure append has nothing to
116
- * fade out, so holding the first 40% of the duration empty just makes the
117
- * animation feel late. Each shape gets the whole duration spent on the parts
118
- * that exist, with adjacent phases overlapping enough to read as one movement.
119
- */
120
- export declare function phasesFor(hasRemoved: boolean, hasAdded: boolean): Phases;
121
- /** {@link phasesFor} under the name a `phaseStrategy` default is looked up by. */
122
- export declare const defaultCodePhases: CodePhaseStrategy;
123
- /** How far through the "everything reflows" phase a frame is, eased. A settled frame (no edit) is always fully moved. */
72
+ /** A pure append has nothing to fade out, so each edit shape spends the whole duration on the phases it has. */
73
+ export declare function defaultCodePhases(hasRemoved: boolean, hasAdded: boolean): Phases;
74
+ /** Eased progress of the move phase; a settled frame (no edit) is fully moved. */
124
75
  export declare function moveProgress(edit: {
125
76
  phases: Phases;
126
77
  progress: number;
127
78
  } | null): number;
128
- /**
129
- * Resolve the per-token opacity multiplier for the current frame from the
130
- * persistent highlight dim plus every active dim transition.
131
- */
79
+ /** Per-token opacity: the persistent highlight dim, overridden by each active dim transition. */
132
80
  export declare function resolveTokenStates(tokenLines: IdLine[], transitions: CodeTransition[], highlightDimOpacity: number | null, highlightedIds: Set<number>): Map<number, TokenState>;
133
81
  //# sourceMappingURL=transitions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transitions.d.ts","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transitions.d.ts","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- /** Sample a piecewise-linear curve (keys → values) at progress `p` in [0, 1]. */
1
+ /** Samples a piecewise-linear curve at progress `p`. */
2
2
  export function sampleCurve(keys, values, p) {
3
3
  if (p <= keys[0])
4
4
  return values[0];
@@ -31,15 +31,8 @@ export function smoothstep(t) {
31
31
  export function makeAnim(id, curve) {
32
32
  return { id, opacity: curve.values, opacityKeys: curve.keys };
33
33
  }
34
- /**
35
- * The phase schedule for an edit, shaped by what the edit actually contains.
36
- *
37
- * A fixed schedule wastes time it does not need: a pure append has nothing to
38
- * fade out, so holding the first 40% of the duration empty just makes the
39
- * animation feel late. Each shape gets the whole duration spent on the parts
40
- * that exist, with adjacent phases overlapping enough to read as one movement.
41
- */
42
- export function phasesFor(hasRemoved, hasAdded) {
34
+ /** A pure append has nothing to fade out, so each edit shape spends the whole duration on the phases it has. */
35
+ export function defaultCodePhases(hasRemoved, hasAdded) {
43
36
  // The overlaps are deliberate and small. Butt-joining the phases leaves the
44
37
  // block sitting empty at its new size for a beat between the old content
45
38
  // leaving and the new content arriving; letting the arrival start while the
@@ -53,16 +46,11 @@ export function phasesFor(hasRemoved, hasAdded) {
53
46
  return { out: null, move: [0, 0.55], in: [0.38, 1] };
54
47
  return { out: null, move: [0, 1], in: null };
55
48
  }
56
- /** {@link phasesFor} under the name a `phaseStrategy` default is looked up by. */
57
- export const defaultCodePhases = phasesFor;
58
- /** How far through the "everything reflows" phase a frame is, eased. A settled frame (no edit) is always fully moved. */
49
+ /** Eased progress of the move phase; a settled frame (no edit) is fully moved. */
59
50
  export function moveProgress(edit) {
60
51
  return edit ? smoothstep(windowProgress(edit.phases.move, edit.progress)) : 1;
61
52
  }
62
- /**
63
- * Resolve the per-token opacity multiplier for the current frame from the
64
- * persistent highlight dim plus every active dim transition.
65
- */
53
+ /** Per-token opacity: the persistent highlight dim, overridden by each active dim transition. */
66
54
  export function resolveTokenStates(tokenLines, transitions, highlightDimOpacity, highlightedIds) {
67
55
  const out = new Map();
68
56
  if (highlightDimOpacity !== null) {
@@ -1 +1 @@
1
- {"version":3,"file":"transitions.js","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { NormalizedColor } from \"@motionscript/sdk\";\nimport type { CodeLayout } from \"./layout\";\nimport { IdLine } from \"./tokens\";\n\n// A single animated token entry owned by one dim transition. Keyed by stable\n// token id rather than (line, tokenIndex) so concurrent animations don't clobber\n// each other when one shifts the indices of tokens the other is animating.\nexport interface AnimToken {\n id: number;\n opacity: number[];\n opacityKeys: number[];\n}\n\n/**\n * A highlight cross-fade: opacity, and nothing else.\n *\n * Structure is untouched, so several of these stack happily and a listing held\n * under a highlight still hits the static-layout cache.\n */\nexport interface DimTransition {\n kind: \"dim\";\n tokens: AnimToken[];\n progress: number;\n}\n\n/**\n * When each part of a structural edit happens, in normalized transition time.\n *\n * The order is what an edit *looks like* being made: what is going away leaves\n * first, what stays reflows into its new place, and only then does what is new\n * appear. Running them together is what made a replacement look like a\n * cross-dissolve of two unrelated listings.\n */\nexport interface Phases {\n /** Removed tokens fade out. Null when the edit removes nothing. */\n out: [number, number] | null;\n /** Surviving tokens travel, and the block resizes. Always present. */\n move: [number, number];\n /** Added tokens fade (and slide) in. Null when the edit adds nothing. */\n in: [number, number] | null;\n}\n\n/**\n * One edit in flight: the structure before, the structure after, and who is\n * doing what.\n *\n * Both endpoints are fixed for the transition's whole life, which is why the\n * two resolved layouts hang off it — they are computed once and reused for\n * every frame the edit spans, rather than rebuilt per frame from a structure\n * that is being mutated underneath.\n */\nexport interface StructuralTransition {\n kind: \"structural\";\n from: IdLine[];\n to: IdLine[];\n removedIds: Set<number>;\n addedIds: Set<number>;\n /**\n * Ids of rows in `to` that did not exist before, and so enter as a whole\n * row rather than as tokens spliced into one.\n *\n * They enter by fading in place, and that is the *only* thing they do. An\n * earlier version slid each one in from the edge the block had just grown\n * along — appended rows rising from below, prepended ones descending, ones\n * spliced into the middle arriving from the left. Every one of those is a\n * claim about where the line came from, and a listing has no such story to\n * tell: text is written where it belongs, it does not travel there. Sliding\n * also puts the row under the neighbours it is meant to sit between for the\n * length of its entrance, which reads as the listing redrawing itself.\n */\n enteringLineIds: Set<number>;\n fromColorById: Map<number, string | undefined>;\n phases: Phases;\n progress: number;\n /** @internal Layout cache — see {@link StructuralTransition}. */\n layoutKey: string | null;\n fromLayout: CodeLayout | null;\n toLayout: CodeLayout | null;\n}\n\n/** A filled band behind whole rows: the first and last line it covers, by line id so it follows them through an edit, and its paint. */\nexport interface RowBand {\n startId: number;\n endId: number;\n color: NormalizedColor;\n radius: number;\n}\n\n/**\n * One set of row bands becoming another. Bands at the same position in both\n * lists travel and recolour; one with no counterpart fades.\n */\nexport interface RowsTransition {\n kind: \"rows\";\n from: RowBand[];\n to: RowBand[];\n progress: number;\n}\n\nexport type CodeTransition = DimTransition | StructuralTransition | RowsTransition;\n\n/**\n * Decides *when* (in normalized transition time) each part of a structural\n * edit happens, given only whether the edit removes/adds anything.\n * `phasesFor` below is the default implementation (exported as\n * `defaultCodePhases`) — pass a `phaseStrategy` of this shape to `<Code>` to\n * replace it with your own timing.\n */\nexport type CodePhaseStrategy = (hasRemoved: boolean, hasAdded: boolean) => Phases;\n\nexport interface TokenState {\n opacity: number;\n}\n\n/** Sample a piecewise-linear curve (keys → values) at progress `p` in [0, 1]. */\nexport function sampleCurve(keys: number[], values: number[], p: number): number {\n if (p <= keys[0]) return values[0];\n if (p >= keys[keys.length - 1]) return values[values.length - 1];\n for (let i = 0; i < keys.length - 1; i++) {\n if (p <= keys[i + 1]) {\n const span = keys[i + 1] - keys[i];\n const local = span === 0 ? 0 : (p - keys[i]) / span;\n return values[i] + (values[i + 1] - values[i]) * local;\n }\n }\n return values[values.length - 1];\n}\n\n/** Progress *within* a phase window, clamped to [0, 1]. A null window is complete. */\nexport function windowProgress(w: [number, number] | null, p: number): number {\n if (!w) return 1;\n const [a, b] = w;\n if (p <= a) return 0;\n if (p >= b) return 1;\n return b === a ? 1 : (p - a) / (b - a);\n}\n\n/** Smooth start and end, so a phase never begins or ends with a velocity step. */\nexport function smoothstep(t: number): number {\n return t * t * (3 - 2 * t);\n}\n\nexport function makeAnim(id: number, curve: { keys: number[]; values: number[] }): AnimToken {\n return { id, opacity: curve.values, opacityKeys: curve.keys };\n}\n\n/**\n * The phase schedule for an edit, shaped by what the edit actually contains.\n *\n * A fixed schedule wastes time it does not need: a pure append has nothing to\n * fade out, so holding the first 40% of the duration empty just makes the\n * animation feel late. Each shape gets the whole duration spent on the parts\n * that exist, with adjacent phases overlapping enough to read as one movement.\n */\nexport function phasesFor(hasRemoved: boolean, hasAdded: boolean): Phases {\n // The overlaps are deliberate and small. Butt-joining the phases leaves the\n // block sitting empty at its new size for a beat between the old content\n // leaving and the new content arriving; letting the arrival start while the\n // reflow's eased tail is still running closes that gap without blurring the\n // order the edit reads in.\n if (hasRemoved && hasAdded) return { out: [0, 0.38], move: [0.10, 0.68], in: [0.52, 1] };\n if (hasRemoved) return { out: [0, 0.45], move: [0.22, 1], in: null };\n if (hasAdded) return { out: null, move: [0, 0.55], in: [0.38, 1] };\n return { out: null, move: [0, 1], in: null };\n}\n\n/** {@link phasesFor} under the name a `phaseStrategy` default is looked up by. */\nexport const defaultCodePhases: CodePhaseStrategy = phasesFor;\n\n/** How far through the \"everything reflows\" phase a frame is, eased. A settled frame (no edit) is always fully moved. */\nexport function moveProgress(edit: { phases: Phases; progress: number } | null): number {\n return edit ? smoothstep(windowProgress(edit.phases.move, edit.progress)) : 1;\n}\n\n/**\n * Resolve the per-token opacity multiplier for the current frame from the\n * persistent highlight dim plus every active dim transition.\n */\nexport function resolveTokenStates(\n tokenLines: IdLine[],\n transitions: CodeTransition[],\n highlightDimOpacity: number | null,\n highlightedIds: Set<number>,\n): Map<number, TokenState> {\n const out = new Map<number, TokenState>();\n\n if (highlightDimOpacity !== null) {\n const dim = highlightDimOpacity;\n for (const line of tokenLines) {\n for (const tok of line.tokens) {\n out.set(tok.id, { opacity: highlightedIds.has(tok.id) ? 1 : dim });\n }\n }\n }\n\n for (const tr of transitions) {\n if (tr.kind !== \"dim\") continue;\n for (const at of tr.tokens) {\n out.set(at.id, { opacity: sampleCurve(at.opacityKeys, at.opacity, tr.progress) });\n }\n }\n\n return out;\n}\n"]}
1
+ {"version":3,"file":"transitions.js","sourceRoot":"","sources":["../src/transitions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { NormalizedColor } from \"@motionscript/sdk\";\nimport type { CodeLayout } from \"./layout\";\nimport { IdLine } from \"./tokens\";\n\n/** Keyed by token id, not (line, index), so a concurrent edit that shifts indices doesn't clobber it. */\nexport interface AnimToken {\n id: number;\n opacity: number[];\n opacityKeys: number[];\n}\n\n/** A highlight cross-fade: opacity only, so several stack and a highlighted listing still hits the static-layout cache. */\nexport interface DimTransition {\n kind: \"dim\";\n tokens: AnimToken[];\n progress: number;\n}\n\n/** When each part of an edit happens, in normalized time: out, then move, then in. Run together, a replacement reads as a cross-dissolve of two listings. */\nexport interface Phases {\n /** Null when the edit removes nothing. */\n out: [number, number] | null;\n /** Surviving tokens travel and the block resizes. */\n move: [number, number];\n /** Null when the edit adds nothing. */\n in: [number, number] | null;\n}\n\n/** One edit in flight. Both endpoints are fixed, so the two layouts are computed once and reused for every frame. */\nexport interface StructuralTransition {\n kind: \"structural\";\n from: IdLine[];\n to: IdLine[];\n removedIds: Set<number>;\n addedIds: Set<number>;\n /** Rows of `to` that enter as a whole row. They only fade in place: text is written where it belongs, and sliding one in puts it under its neighbours. */\n enteringLineIds: Set<number>;\n fromColorById: Map<number, string | undefined>;\n phases: Phases;\n progress: number;\n /** @internal */\n layoutKey: string | null;\n fromLayout: CodeLayout | null;\n toLayout: CodeLayout | null;\n}\n\n/** A filled band behind whole rows: the first and last line it covers, by line id so it follows them through an edit, and its paint. */\nexport interface RowBand {\n startId: number;\n endId: number;\n color: NormalizedColor;\n radius: number;\n}\n\n/** Bands at the same position in both lists travel and recolour; one with no counterpart fades. */\nexport interface RowsTransition {\n kind: \"rows\";\n from: RowBand[];\n to: RowBand[];\n progress: number;\n}\n\nexport type CodeTransition = DimTransition | StructuralTransition | RowsTransition;\n\n/** Decides the {@link Phases} from whether the edit removes or adds anything; `defaultCodePhases` is the default. */\nexport type CodePhaseStrategy = (hasRemoved: boolean, hasAdded: boolean) => Phases;\n\nexport interface TokenState {\n opacity: number;\n}\n\n/** Samples a piecewise-linear curve at progress `p`. */\nexport function sampleCurve(keys: number[], values: number[], p: number): number {\n if (p <= keys[0]) return values[0];\n if (p >= keys[keys.length - 1]) return values[values.length - 1];\n for (let i = 0; i < keys.length - 1; i++) {\n if (p <= keys[i + 1]) {\n const span = keys[i + 1] - keys[i];\n const local = span === 0 ? 0 : (p - keys[i]) / span;\n return values[i] + (values[i + 1] - values[i]) * local;\n }\n }\n return values[values.length - 1];\n}\n\n/** Progress *within* a phase window, clamped to [0, 1]. A null window is complete. */\nexport function windowProgress(w: [number, number] | null, p: number): number {\n if (!w) return 1;\n const [a, b] = w;\n if (p <= a) return 0;\n if (p >= b) return 1;\n return b === a ? 1 : (p - a) / (b - a);\n}\n\n/** Smooth start and end, so a phase never begins or ends with a velocity step. */\nexport function smoothstep(t: number): number {\n return t * t * (3 - 2 * t);\n}\n\nexport function makeAnim(id: number, curve: { keys: number[]; values: number[] }): AnimToken {\n return { id, opacity: curve.values, opacityKeys: curve.keys };\n}\n\n/** A pure append has nothing to fade out, so each edit shape spends the whole duration on the phases it has. */\nexport function defaultCodePhases(hasRemoved: boolean, hasAdded: boolean): Phases {\n // The overlaps are deliberate and small. Butt-joining the phases leaves the\n // block sitting empty at its new size for a beat between the old content\n // leaving and the new content arriving; letting the arrival start while the\n // reflow's eased tail is still running closes that gap without blurring the\n // order the edit reads in.\n if (hasRemoved && hasAdded) return { out: [0, 0.38], move: [0.10, 0.68], in: [0.52, 1] };\n if (hasRemoved) return { out: [0, 0.45], move: [0.22, 1], in: null };\n if (hasAdded) return { out: null, move: [0, 0.55], in: [0.38, 1] };\n return { out: null, move: [0, 1], in: null };\n}\n\n/** Eased progress of the move phase; a settled frame (no edit) is fully moved. */\nexport function moveProgress(edit: { phases: Phases; progress: number } | null): number {\n return edit ? smoothstep(windowProgress(edit.phases.move, edit.progress)) : 1;\n}\n\n/** Per-token opacity: the persistent highlight dim, overridden by each active dim transition. */\nexport function resolveTokenStates(\n tokenLines: IdLine[],\n transitions: CodeTransition[],\n highlightDimOpacity: number | null,\n highlightedIds: Set<number>,\n): Map<number, TokenState> {\n const out = new Map<number, TokenState>();\n\n if (highlightDimOpacity !== null) {\n const dim = highlightDimOpacity;\n for (const line of tokenLines) {\n for (const tok of line.tokens) {\n out.set(tok.id, { opacity: highlightedIds.has(tok.id) ? 1 : dim });\n }\n }\n }\n\n for (const tr of transitions) {\n if (tr.kind !== \"dim\") continue;\n for (const at of tr.tokens) {\n out.set(at.id, { opacity: sampleCurve(at.opacityKeys, at.opacity, tr.progress) });\n }\n }\n\n return out;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionscript/code",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-alpha.0",
4
4
  "type": "module",
5
5
  "description": "Animated, syntax-highlighted code blocks for Motion Script, with token-level append, insert, remove, replace, and highlight transitions.",
6
6
  "repository": {
@@ -46,7 +46,7 @@
46
46
  "license": "Apache-2.0",
47
47
  "author": "Motionscript",
48
48
  "peerDependencies": {
49
- "@motionscript/sdk": "^0.1.0"
49
+ "@motionscript/sdk": "^0.1.1-alpha.0"
50
50
  },
51
51
  "dependencies": {
52
52
  "@lezer/common": "^1.5.2",
@@ -74,8 +74,8 @@
74
74
  "typescript": "^6.0.3",
75
75
  "typescript-eslint": "^8.48.0",
76
76
  "vitest": "^4.1.5",
77
- "@motionscript/core": "0.1.0",
78
- "@motionscript/sdk": "0.1.0"
77
+ "@motionscript/core": "0.1.1-alpha.0",
78
+ "@motionscript/sdk": "0.1.1-alpha.0"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths && node ../../../scripts/build-browser.mjs",
package/src/code-range.ts CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * A range over a code document. Lines and columns are 1-indexed.
3
- *
4
- * `endLine`/`endCol` may be `Infinity` to mean "to the end of the line" or
5
- * "to the end of the document".
6
- */
1
+ /** Lines and columns are 1-indexed; `endLine`/`endCol` may be `Infinity` for the end of the line or document. */
7
2
  export interface CodeRange {
8
3
  startLine: number;
9
4
  startCol: number;
@@ -11,14 +6,7 @@ export interface CodeRange {
11
6
  endCol: number;
12
7
  }
13
8
 
14
- /**
15
- * Create a range that spans `length` characters starting at (line, col).
16
- * If `length` is omitted, the range extends to the end of the line.
17
- *
18
- * Example:
19
- * word(1, 3, 3) // line 1, column 3, spans 3 characters
20
- * word(1, 3) // line 1, column 3, to end of line
21
- */
9
+ /** `length` characters from (line, col); to the end of the line when omitted. */
22
10
  export function word(line: number, col: number, length?: number): CodeRange {
23
11
  return {
24
12
  startLine: line,
@@ -28,14 +16,7 @@ export function word(line: number, col: number, length?: number): CodeRange {
28
16
  };
29
17
  }
30
18
 
31
- /**
32
- * Create a range that covers entire lines from `from` to `to` (inclusive).
33
- * If `to` is omitted, only line `from` is covered.
34
- *
35
- * Example:
36
- * lines(1, 3) // lines 1 through 3
37
- * lines(2) // line 2 only
38
- */
19
+ /** Whole lines `from` to `to` inclusive; just `from` when `to` is omitted. */
39
20
  export function lines(from: number, to?: number): CodeRange {
40
21
  return {
41
22
  startLine: from,
@@ -45,17 +26,7 @@ export function lines(from: number, to?: number): CodeRange {
45
26
  };
46
27
  }
47
28
 
48
-
49
-
50
- /**
51
- * Immutable, chainable, iterable builder over multiple ranges — mirrors
52
- * {@link FillChain}/`Fills` in `@motionscript/sdk`. Every method returns a
53
- * *new* chain with one more range appended, so the next range-kind method is
54
- * called directly rather than through a separate combinator:
55
- *
56
- * @example
57
- * CodeRanges.lines(2).word(5, 1, 3) // a 2-range selection, one expression
58
- */
29
+ /** Immutable chain of ranges: each method returns a new chain, as in `CodeRanges.lines(2).word(5, 1, 3)`. */
59
30
  export class CodeRangeChain {
60
31
  constructor(public readonly list: CodeRange[] = []) { }
61
32
 
@@ -81,34 +52,23 @@ export class CodeRangeChain {
81
52
  }
82
53
  }
83
54
 
84
- /**
85
- * Entry point for building multi-range selections fluently — an empty
86
- * {@link CodeRangeChain}, so `CodeRanges.lines(2)` *is* `new
87
- * CodeRangeChain().lines(2)`. `word`/`lines` stay directly importable and
88
- * usable standalone for a single range; reach for `CodeRanges` when a call
89
- * needs more than one.
90
- */
55
+ /** An empty {@link CodeRangeChain}, the start of a multi-range selection. */
91
56
  export const CodeRanges = new CodeRangeChain();
92
57
 
93
- /**
94
- * Convert a CodeRange to absolute character offsets [start, end) in the joined
95
- * source text, given the per-line lengths of that source. Lines/columns are
96
- * clamped into the document so out-of-bounds inputs produce a valid range.
97
- */
58
+ /** Absolute [start, end) offsets in the joined source, clamped into the document. */
98
59
  export function rangeToCharOffsets(
99
60
  r: CodeRange,
100
61
  lineLengths: number[],
101
62
  ): { start: number; end: number } {
102
63
  if (lineLengths.length === 0) return { start: 0, end: 0 };
103
64
 
104
- // Cumulative line starts in the joined string (lines joined with '\n').
105
65
  const lineStart: number[] = new Array(lineLengths.length);
106
66
  let acc = 0;
107
67
  for (let i = 0; i < lineLengths.length; i++) {
108
68
  lineStart[i] = acc;
109
69
  acc += lineLengths[i] + 1; // +1 for the joining newline
110
70
  }
111
- const docEnd = acc > 0 ? acc - 1 : 0; // strip trailing newline
71
+ const docEnd = acc > 0 ? acc - 1 : 0;
112
72
 
113
73
  const clampLine = (n: number) =>
114
74
  Math.max(0, Math.min(lineLengths.length - 1, n - 1)); // to 0-indexed
@@ -122,8 +82,7 @@ export function rangeToCharOffsets(
122
82
  const sColMax = lineLengths[sLine];
123
83
  const eColMax = lineLengths[eLine];
124
84
 
125
- // Columns are 1-indexed; clamp to [1, lineLength+1] so endCol can sit past
126
- // the last char (one-past-end).
85
+ // endCol may sit one past the last character.
127
86
  const sCol = Math.max(0, Math.min(sColMax, r.startCol - 1));
128
87
  const eCol = endCol === Infinity
129
88
  ? eColMax
@@ -134,10 +93,7 @@ export function rangeToCharOffsets(
134
93
  return { start: Math.min(start, end), end: Math.max(start, end) };
135
94
  }
136
95
 
137
- /**
138
- * Convert [start, end) character offsets in joined source to a CodeRange.
139
- * Lines/cols in the returned range are 1-indexed.
140
- */
96
+ /** The 1-indexed range for [start, end) offsets in the joined source. */
141
97
  export function charOffsetsToRange(
142
98
  start: number,
143
99
  end: number,