diff --git a/e2e/stylesheet_render/src/stylesheet_render.gleam b/e2e/stylesheet_render/src/stylesheet_render.gleam index 8d72baa..9f1528b 100644 --- a/e2e/stylesheet_render/src/stylesheet_render.gleam +++ b/e2e/stylesheet_render/src/stylesheet_render.gleam @@ -17,8 +17,7 @@ pub type Msg { pub fn main() { let assert Ok(render) = - craft_options.default() - |> craft_options.node() + craft_options.node() |> craft.setup() let assert Ok(_) = diff --git a/src/cache.ffi.mjs b/src/cache.ffi.mjs index 991499a..8adda7a 100644 --- a/src/cache.ffi.mjs +++ b/src/cache.ffi.mjs @@ -3,6 +3,18 @@ import { StyleSheet } from './stylesheet.ffi.mjs' export let cache +/** + * The cache maintains a structure similar to a VDOM, but for CSS. It tries to + * works on the more optimized way, by using two different cache, to store the + * current state, and the old state, and apply a diff correctly. + * The way it works is to first call the `prepare` function, and then to add + * add style by using the `store` function. To avoid doing the hard work twice, + * the function `persist` will try to get the style in the old cache, and + * transfer it to the new cache, to make sure it will not be destroyed in the + * diff. + * It uses a virtual StyleSheet, in which it's possible to insert or delete rules + * and allow to build the real stylesheet once the diff is made. + */ export class Cache { #memoCache #activeCache @@ -21,6 +33,9 @@ export class Cache { this.#activeCache = new Map() } + // Compute the predefined classes C = (Keys(Old) ∩ Keys(New)) + // Remove the keys defined by Keys(Old) - C. + // Insert the keys defined by Keys(New) - C. diff() { const keys = new Set() for (const key of this.#activeCache.keys()) keys.add(key) @@ -84,7 +99,9 @@ export class Cache { } // Insert the styles in the stylesheet. - // It inserts medias, selectors and index rules. + // It inserts medias, selectors and index rules. It inserts first the rule, + // then the selectors, and then the media, to respect the usual order in a + // standard CSS sheet, and to respect precedence of the styles. #insertStyles(klass) { const indexRules = [] const { definitions } = klass @@ -107,7 +124,8 @@ export function createCache(options) { return cache } -export function prepareCache(cache) { +export function prepareCache(cache_) { + cache = cache_ cache.prepare() } diff --git a/src/craft.ffi.mjs b/src/craft.ffi.mjs index 1bd0ce1..0978536 100644 --- a/src/craft.ffi.mjs +++ b/src/craft.ffi.mjs @@ -1,6 +1,8 @@ import { cache } from './cache.ffi.mjs' import * as helpers from './helpers.ffi.mjs' +// The Style data structure being a recursive data, computeProperties traverse +// the data structure and collect the properties with their context. function computeProperties(rawProperties, indent = 2) { const properties = rawProperties.toArray() const init = { properties: [], medias: [], classes: [], pseudoSelectors: [], indent } @@ -39,6 +41,8 @@ function computeProperties(rawProperties, indent = 2) { }, init) } +// Compute classes by using the class definitions, and by wrapping them in the +// correct class declarations, to be CSS compliant. function computeClasses(id, computedProperties) { const { properties, medias, classes, pseudoSelectors } = computedProperties const classDef = helpers.wrapClass(id, properties, 0) @@ -68,11 +72,15 @@ export function compileClass(styles, classId) { return { name, className } } +// Memoize the class definitions in the cache. +// Once memoized, it's impossible to un-memoize a class. export function memo(klass) { cache.memoize(klass) return klass } +// Extract the name of the Class type, which is an opaque type for +// the type { name: string, className: string } export function toString({ name }) { return name } diff --git a/src/craft/media.gleam b/src/craft/media.gleam index 2dba6a1..ce40ed9 100644 --- a/src/craft/media.gleam +++ b/src/craft/media.gleam @@ -1,4 +1,13 @@ -//// Defines media queries directly with functions. +//// Define media queries directly with functions. +//// Refer to the craft module to get more details on the usage. +//// +//// ## Advanced usage +//// +//// Media queries can be rather complex, and the module tries to give all +//// features in a usable way. A media query takes form (property: value) and +//// can be combined, like (orientation: landscape or min-width: 1000px). +//// Those media queries can be created by using the corresponding `and`, +//// `or` or `not` functions. import craft/size.{type Size, to_string as to_str} import gleam/string @@ -79,7 +88,7 @@ fn q_to_str(query: Query) { } } -/// Mainly internal function, can be used if you need to go from a media query to a String +/// Internal function, can be used if you need to go from a media query to a String /// in case you're building on top of craft. pub fn to_string(query: Query) { let content = q_to_str(query) diff --git a/src/craft/options.gleam b/src/craft/options.gleam index 2b251c2..a960b4a 100644 --- a/src/craft/options.gleam +++ b/src/craft/options.gleam @@ -1,3 +1,8 @@ +//// Defines Options to setup the runtime of craft. +//// The Options could right now be a `style` node injected in the DOM, or on +//// a CSSStyleSheet, adopted directly on the document. + +/// Internal use. pub opaque type StyleSheet { Node Browser @@ -7,18 +12,16 @@ pub opaque type Options { Options(stylesheet: StyleSheet) } -pub fn default() { - Options(Node) -} - -pub fn node(options: Options) -> Options { - Options(..options, stylesheet: Node) +pub fn node() -> Options { + Options(stylesheet: Node) } -pub fn browser(options: Options) -> Options { - Options(..options, stylesheet: Browser) +pub fn browser() -> Options { + Options(stylesheet: Browser) } +/// Internal function, can be used if you need to go from a StyleSheet to a String +/// in case you're building on top of craft. Used in FFI at the moment. pub fn stylesheet_to_string(stylesheet: StyleSheet) -> String { case stylesheet { Node -> "node" diff --git a/src/craft/size.gleam b/src/craft/size.gleam index 1593207..99d614c 100644 --- a/src/craft/size.gleam +++ b/src/craft/size.gleam @@ -5,6 +5,10 @@ import gleam/int import gleam/float import gleam/string +/// Size defines a CSS Unit. It can be either `px`, `pt`, `vh`, `vw`, `em`, +/// `rem`, `lh`, `rlh`, `ch`, `%`. To instanciate a Size, use the corresponding +/// functions. Every unit exposes two functions: the Int function (like `px(0)`) +/// and the Float version suffixed by an underscore (like `px_(0.0)`). pub opaque type Size { Px(Float) Pt(Float) @@ -98,7 +102,7 @@ pub fn ch_(value: Float) { Ch(value) } -/// Mainly internal function, can be used if you need to go from a Size to a String +/// Internal function, can be used if you need to go from a Size to a String /// in case you're building on top of craft. pub fn to_string(size) { case size { diff --git a/src/helpers.ffi.mjs b/src/helpers.ffi.mjs index 73cd098..75d2b09 100644 --- a/src/helpers.ffi.mjs +++ b/src/helpers.ffi.mjs @@ -20,17 +20,22 @@ export function getFunctionName() { return stack.split('\n').slice(1, 5).join('\n') } -export function deepEqual(args, previousArgs) { - const constants = ['string', 'number', 'boolean'] - if (constants.includes(typeof args) || constants.includes(typeof previousArgs)) return args === previousArgs - for (const value in args) { - if (!(value in previousArgs)) return false - const isSame = deepEqual(args[value], previousArgs[value]) +// Compare two data structures to check if they're the same. +export function deepEqual(a, b) { + const consts = ['string', 'number', 'boolean'] + if (consts.includes(typeof a) || consts.includes(typeof b)) return a === b + for (const value in a) { + if (!(value in b)) return false + const isSame = deepEqual(a[value], b[value]) if (!isSame) return false } return true } +// A Style property can be of four types: a class composition, a property definition +// a pseudo-selector definitions or a media query definition. +// They're defined in Gleam, and the class is opaque, so the only way is to +// read in the content of the object to check the interesting fields. export function determineStyleType(style) { if ('class_name' in style && typeof style.class_name === 'string') { return 'compose' @@ -43,11 +48,18 @@ export function determineStyleType(style) { } } +// Take a class definition, and turns it to something like +// .className { +// property1: value; +// property2: value; +// } export function wrapClass(id, properties, indent_, pseudo = '') { const baseIndent = indent(indent_) return [`${baseIndent}.${id}${pseudo} {`, ...properties, `${baseIndent}}`].join('\n') } +// Turn the different components of a property into the correct CSS property. +// I.e. property: value [!important]; export function computeProperty(indent_, property) { const baseIndent = indent(indent_) const key = `${baseIndent}${property.key}`