Improvement: decoration coordinates are now compared to the document (the documents length and/or number of lines) and rejected if too large
Improvement: improve animation heuristics for ECMAScript
Improvement: fully support @scope in CSS
Feature: add minCols
option to animateHTML()
and toAnimationHTML()
, which sets a minimum width for animations
Improvement: support range syntax in CSS media queries
Improvement: improve general animation heuristics
Bugfix: do not throw away trailing newlines
Feature: optional comments in JSON! Set the option comments
to true
when instantiating the JSON language module to unlock support for C-style line and block comments:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
// "comments" is optional and defaults to "false"
let rfc8259 = json();
let normalJSON = animateHTML([{ code: "[42]" }], {
tabSize: 2,
language: rfc8259,
});
// unlocks line and block comments
let jsonWithComments = json({ comments: true });
let withComments = animateHTML([{ code: "[42 /* Hello */ ] // World" }], {
tabSize: 2,
language: jsonWithComments,
});
123456789101112131415161718import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
// "comments" is optional and defaults to "false"
let rfc8259 = json();
let normalJSON = animateHTML([{ code: "[42]" }], {
tabSize: 2,
language: rfc8259,
});
// unlocks line and block comments
let jsonWithComments = json({ comments: true });
let withComments = animateHTML([{ code: "[42 /* Hello */ ] // World" }], {
tabSize: 2,
language: jsonWithComments,
});
Bugfix: protect against problems that arose when certain over-enthusiastic build tools optimized (arguably redundant) SVG attributes away, leading to some CSS rules not longer matching where they should.
Bugfix: prevent word wrapping in selectable text under more circumstances.
Feature: conditional themes. This feature enables Code.Movie to generate CSS from themes with an attached condition, such as a media query or a parent selector. To use this feature, pass an array of themes with conditions to animateHTML()
or toAnimationHTML()
in place of a single theme object:
import {
toAnimationHTML,
monokaiLight,
monokaiDark,
} from "@codemovie/code-movie";
// Example: use dark or light theme dependent on a media query
const html1 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
{ theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
],
});
// Example: use dark or light theme dependent on a parent class
const html2 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: ":root.isLight" },
{ theme: monokaiDark, condition: ":root.isDark" },
],
});
123456789101112131415161718192021import {
toAnimationHTML,
monokaiLight,
monokaiDark,
} from "@codemovie/code-movie";
// Example: use dark or light theme dependent on a media query
const html1 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
{ theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
],
});
// Example: use dark or light theme dependent on a parent class
const html2 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: ":root.isLight" },
{ theme: monokaiDark, condition: ":root.isDark" },
],
});
Improvement: better animation heuristics, especially for CSS and several ECMAScript features.
Bugfix: properly type the instanceof
keyword in ECMAScript as well as async method definitions and setters in ECMAScript object literals.
Bugfix: fix --ui-status-bar-enabled
not working in Monokai Dark theme
Bugfix: repair event more CSS regressions. Specifically, exclude svg elements from the css reset: This should make decoration underlines work again.
Bugfix: repair a few more CSS regressions like in 0.0.26.
Bugfix: repair a few CSS regressions that occurred due to the introduction of the style isolation improvements in 0.0.25. Specifically, the CSS reset had unintended side effects when global styles did not use certain defaults like text-align: center
.
Feature: custom style isolation. All generated CSS is scoped to to animation element it was generated for, but this does not stop third-party styles from messing with the generated CSS. This is, in part at least, by design - users should be able to override any and all styles that Code.Movie generates! But the default approach to style isolation, which consisted of nothing more than prefixing all selectors with an auto-generated class, turned out to be insufficient for real-world use cases. This can be dealt with thanks to the new output option wrapCSS
, which enables custom style isolation. The default approach is still to wrap everything in a class, but you can modify this by supplying custom wrapper strings:
import { animateHTML } from "@codemovie/code-movie";
import { json } from "@codemovie/code-movie/language/json";
const html = animateHTML(arrayOfFrames, {
language: json(),
tabSize: 2,
// Use the element's id in an actual ID selector to beef up the specificity.
// You could also make use of @layer, @scope, or simple make the selector even
// more specific to fit your needs!
wrapCSS: (id, content) => `#${id}{${content}}`,
});
1234567891011import { animateHTML } from "@codemovie/code-movie";
import { json } from "@codemovie/code-movie/language/json";
const html = animateHTML(arrayOfFrames, {
language: json(),
tabSize: 2,
// Use the element's id in an actual ID selector to beef up the specificity.
// You could also make use of @layer, @scope, or simple make the selector even
// more specific to fit your needs!
wrapCSS: (id, content) => `#${id}{${content}}`,
});
The auto-generated CSS now also contains a very basic CSS reset that applies all: unset
to all elements inside the animation. This, together with the new wrapCSS
option makes sure that you can precisely pick the right amount of CSS isolation for your animations.
Bugfix: text content in <textarea>
elements in HTML now gets the same treatment as other text content received in 0.0.23. Entity and character references are now also categorized as distinct types, as they should always have been.
Bugfix: text content in HTML should always have been chopped up into word-level tokens, but this was apparently never implemented. This is fixed in 0.0.23.
This release ships with two additional built-in themes. Both Monokai Dark and Monokai Light are quite different from the default theme (which is still the default) in that they use several new features to render quite a bit more than just text:
The themes themselves can also be customized with CSS variables - read the docs for more details!
The two new themes would not be possible without a massive upgrade to the options for styling animations. If you wanted to render anything interesting in your animations before, you had to use the CSS pseudo classes ::before
and ::after
on the root element. This not only required you to be a true CSS wizard, but also limited you to at most two elements to play with. As of version 0.0.22, the HTML output of every animation gets 10 additional <div>
elements named cm-box0
, cm-box1
... cm-box9
that exist purely as hooks for style customization. You (or your friendly CSS wizard neighbor) can use each box to draw a new element, such as a editor window with borders and drop shadows, and decorative elements for the editor window (like title and status bars, or a different background and/or border for the gutter). The new themes are good examples of what's possible.
Also there's several new CSS hooks: --total-frames
gets you the number of frames in the animation, while --current-frame
reflects the current frame number (starting at 1). In case you intend to render the animation state via the string-only content property, --total-frames-string
and --current-frame-string
are also available. --has-gutter-decorations
indicates whether the animation contains any gutter decorations, which may warrant resizing the line number column. Similarly, --has-line-numbers
indicated whether the animation's style settings result in line numbers being enabled. All this, taken together, makes the two new themes in this release possible.
And yes, the implementation of this "feature" consists of little more than adding 10 divs with hard-coded class names to the output HTML. This actually gives you 32 boxes to play with, because you get to use the divs themselves, their ::before
and ::after
pseudo elements and ::before
and ::after
on the animation element itself. And 32 boxes ought to be enough for anybody.
--cm-animate-height
can control whether the animation's container element animates its actual heightimplements
keyword and const generics.Decorations are finally available! You can add highlights, gutter icons and underlines (and much more once you get creative with CSS) to your animations.
This library auto-generates class names for the animations based on a seed value. This seed value can be changed via the option rootClassSeed
when calling animateHTML()
or toAnimationHTML()
, but this was (and remains) optional:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let html = animateHTML(
[
/* keyframes */
],
{
tabSize: 2,
language: json(),
rootClassSeed: 42, // optional
},
);
12345678910111213import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let html = animateHTML(
[
/* keyframes */
],
{
tabSize: 2,
language: json(),
rootClassSeed: 42, // optional
},
);
This seed value used to default to 0, which meant that (unless a custom seed value was provided) two animations in a single page would probably suffer from class collisions. This default value arguably did more harm than good, so 0.0.21 changes it to an auto-incrementing number, starting with a random large integer. If you rely on reproducing the same class names each time you call animateHTML()
or toAnimationHTML()
, be sure to pass the same number as the rootClassSeed
option.
fromStringsToScene
and animateHTML
handle empty frame arrays without throwing cryptic errorsusing
in TypeScriptanimateHTML(input, options)
This new function combines fromStringsToScene()
and toAnimationHTML()
into one slightly more convenient function. Where you previously had to wrangle two functions and two options objects...
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = toAnimationHTML(
fromStringsToScene(keyframes, {
tabSize: 2,
language: json(),
}),
{
rootClassSeed: 42,
},
);
12345678910111213141516import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = toAnimationHTML(
fromStringsToScene(keyframes, {
tabSize: 2,
language: json(),
}),
{
rootClassSeed: 42,
},
);
You can now just import and call a single function to get the same effect:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = animateHTML(keyframes, {
tabSize: 2,
language: json(),
rootClassSeed: 42,
});
123456789101112import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = animateHTML(keyframes, {
tabSize: 2,
language: json(),
rootClassSeed: 42,
});
The options object combines the options available to fromStringsToScene()
and toAnimationHTML()
into a single object. The old functions fromStringsToScene()
and toAnimationHTML()
are still available and will remain supported for the forseeable future.
while
loops and super
The theme object has always contained some unused data for decorations (code underlines, gutter icons an similar). In preparation for the upcoming release of actual decoration support, the schema for this data has changed. If you have created your own theme object previously, you will need to perform the following modifications:
version
to 1
layers.decorations
with the following:layers.decorations
let myTheme = {
version: 1,
/* ... other things... */
layers: {
/* ... other things... */
decorations: {
gutter: {
kind: "DECORATION_GUTTER",
offset: [0, 0],
},
line: {
kind: "DECORATION_LINE",
background: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFF2BC",
},
},
},
foreground: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
},
},
text: {
kind: "DECORATION_TEXT",
background: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFFF00",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
foreground: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
},
},
/* ... other things... */
},
};
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182let myTheme = {
version: 1,
/* ... other things... */
layers: {
/* ... other things... */
decorations: {
gutter: {
kind: "DECORATION_GUTTER",
offset: [0, 0],
},
line: {
kind: "DECORATION_LINE",
background: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFF2BC",
},
},
},
foreground: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
},
},
text: {
kind: "DECORATION_TEXT",
background: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFFF00",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
foreground: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
},
},
/* ... other things... */
},
};
--line-numbers-width
contains the width of the line number column (if line numbers are enabled)typeof
operator, the ?.
operator the as
keyword (in both TS and JS contexts)Infinity
Support for Rust has landed! Simply import the language module @codemovie code-movie/languages/rust
and go from there. Rust is a complex language that I personally know next to nothing about, so you can expect this first version to maybe miss some syntax and definitely have some sub-par animation heuristics. You can also expect this to improve over time.
Languages modules now benefit from code splitting, which removes a lot of previously duplicated code. This only applies to ESM modules but should greatly reduce the overall bundle size for all projects. All language modules that leverage the common parsing infrastructure or that make use of other languages modules (like JS and CSS being used in HTML to support <style>
and <script>
) will benefit from this change.
with
statement--cm-content-margin-left
was erroneously always multiplied by twodeclare
keyword and associated syntax, like namespacesaccessor
keywordAdding support for Elixir in 0.0.13 increased the bundle size considerably, which made modularizing the entire project necessary. Languages are now modules that need to be imported and instantiated explicitly:
// Main module imports like before
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
// NEW: import the language module you want to use
import json from "@codemovie/code-movie/languages/json";
// NEW: instantiate the language. Some languages can be configured to support
// certain features by passing options to the language function (eg. enable JSX
// support in ECMAScript by passing `{ jsx: true }`)
const language = json();
let sceneData = fromStringsToScene(
[
/* ...keyframes... */
],
{
tabSize: 2,
language, // NEW: now the language object instead of a string
},
);
let html = toAnimationHTML(sceneData);
12345678910111213141516171819202122// Main module imports like before
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
// NEW: import the language module you want to use
import json from "@codemovie/code-movie/languages/json";
// NEW: instantiate the language. Some languages can be configured to support
// certain features by passing options to the language function (eg. enable JSX
// support in ECMAScript by passing `{ jsx: true }`)
const language = json();
let sceneData = fromStringsToScene(
[
/* ...keyframes... */
],
{
tabSize: 2,
language, // NEW: now the language object instead of a string
},
);
let html = toAnimationHTML(sceneData);
This change breaks the following APIs:
InputOptions
passed to fromStringsToScene()
as its second argument must now be an object returned from a language function instead of a string referring to the language namefromStringsToScene()
now returns an object containing the Scene
object and the language object that was originally passed in the InputOptions
toAnimationHTML()
must now be an object containing the scene
object and the language object used to create the scene (in other words, it still takes the output of fromStringsToScene()
as its first argument, but the actual object is slightly different)SUPPORTED_LANGUAGES
(set of language names) and Language
(TypeScript union type of language names) do no longer existThe following language modules are currently available:
@codemovie/code-movie/languages/css
@codemovie/code-movie/languages/ecmascript
(for JavaScript and TypeScript)@codemovie/code-movie/languages/elixir
@codemovie/code-movie/languages/html
@codemovie/code-movie/languages/json
@codemovie/code-movie/languages/plaintext
The language module @codemovie/code-movie/languages/ecmascript
enables support for JSX in both TypeScript and vanilla JavaScript:
import ecmascript from "@codemovie/code-movie/languages/ecmascript";
let regularJavaScript = ecmascript();
let typeScript = ecmascript({ ts: true });
let javaScriptWithJSX = ecmascript({ jsx: true });
let typeScriptWithJSX = ecmascript({ ts: true, jsx: true });
123456import ecmascript from "@codemovie/code-movie/languages/ecmascript";
let regularJavaScript = ecmascript();
let typeScript = ecmascript({ ts: true });
let javaScriptWithJSX = ecmascript({ jsx: true });
let typeScriptWithJSX = ecmascript({ ts: true, jsx: true });
ranges
and decorations
The fields ranges
and decorations
on InputFrames
(the first argument to fromStringsToScene()
) are now optional and default to empty arrays. They will be empty for most frames anyway and filling in the blanks is trivial. Plus it makes the tutorial shorter!
The bugfixes all revolve around plugging small to medium-sized holes in language support:
do
blocks to reliably produce non-confusing resultsreadonly
in index signatures and the infer
keyword were not properly detectedswitch
bodies were not properly detectedbreak
and continue
keywords were not properly detectedAdjusted some magic numbers in the token matchup functions. This should provide an overall improved diffing behavior.
Improve stability and add support for Elixir.
Initial public release.