Code.Movie

Changelog

v 0.0.32

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

v 0.0.31

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

v 0.0.30

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,
});
123456789101112131415161718
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,
});

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.

v 0.0.29

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" },
  ],
});
123456789101112131415161718192021
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" },
  ],
});

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

v 0.0.28

Bugfix: repair event more CSS regressions. Specifically, exclude svg elements from the css reset: This should make decoration underlines work again.

v 0.0.27

Bugfix: repair a few more CSS regressions like in 0.0.26.

v 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.

v 0.0.25

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}}`,
});
1234567891011
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}}`,
});

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.

v 0.0.24

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.

v 0.0.23

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.

v 0.0.22

Feature: Monokai Dark and Monokai Light themes

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!

Feature: styling customization upgrades

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.

Bugfixes and other minor changes in 0.0.22

v 0.0.21

Feature: Decorations

Decorations are finally available! You can add highlights, gutter icons and underlines (and much more once you get creative with CSS) to your animations.

Breaking: auto-incrementing root class seed

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
  },
);
12345678910111213
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
  },
);

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.

Bugfixes in 0.0.21

v 0.0.20

Feature: animateHTML(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,
  },
);
12345678910111213141516
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,
  },
);

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,
});
123456789101112
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,
});

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.

Bugfixes in 0.0.20

v 0.0.19

Bugfixes in 0.0.19

v 0.0.18

Breaking: theme object changes

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:

  1. set version to 1
  2. replace layers.decorations with the following:
Show the new content of 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... */
  },
};
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
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... */
  },
};

Other improvements in 0.0.18

Bugfixes in 0.0.18

v 0.0.17

New language: Rust

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.

Improvement: Code Splitting

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.

Bugfixes in 0.0.17

Other changes in 0.0.17

v 0.0.16

Bugfixes in 0.0.16

v 0.0.15

Breaking: language modules

Adding 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:

The following language modules are currently available:

Improvement: JSX support for JavaScript and TypeScript

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 });
123456
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 });

Improvement: optional 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!

Bugfixes in 0.0.15

The bugfixes all revolve around plugging small to medium-sized holes in language support:

Other changes in in 0.0.15

Adjusted some magic numbers in the token matchup functions. This should provide an overall improved diffing behavior.

v 0.0.14

Improve stability and add support for Elixir.

v 0.0.13

Initial public release.