@ng-native/fabric 0.1.3 → 0.3.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.
package/dist/host.d.ts CHANGED
@@ -104,6 +104,13 @@ export declare abstract class HostEngine {
104
104
  * that measures when content changes, as a browser does, there is nothing to do.
105
105
  */
106
106
  remeasure(_node: HostNode): void;
107
+ /**
108
+ * Style `node` as an element of the template `like` is written in, rather than of the one that
109
+ * created it, or as its own again when `like` is null: the content view a scroll view makes for
110
+ * its children, which the app styles through a class of its own and so from its own component
111
+ * styles.
112
+ */
113
+ adoptScope(_node: HostNode, _like: HostNode | null): void;
107
114
  /**
108
115
  * Take the user to a node: a text input gets the cursor, and anything else is brought on
109
116
  * screen. What a DOM element's own `focus()` does, for code that calls it on an element.
package/dist/host.js CHANGED
@@ -22,6 +22,13 @@ export class HostEngine {
22
22
  * that measures when content changes, as a browser does, there is nothing to do.
23
23
  */
24
24
  remeasure(_node) { }
25
+ /**
26
+ * Style `node` as an element of the template `like` is written in, rather than of the one that
27
+ * created it, or as its own again when `like` is null: the content view a scroll view makes for
28
+ * its children, which the app styles through a class of its own and so from its own component
29
+ * styles.
30
+ */
31
+ adoptScope(_node, _like) { }
25
32
  /**
26
33
  * Take the user to a node: a text input gets the cursor, and anything else is brought on
27
34
  * screen. What a DOM element's own `focus()` does, for code that calls it on an element.
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AA4EA;;;;;GAKG;AACH,MAAM,OAAgB,UAAU;IAwB9B;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAgB;QACrB,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAe,IAAS,CAAC;IAEnC;;;OAGG;IACH,KAAK,CAAC,KAAe,IAAS,CAAC;IAE/B;;;OAGG;IACH,MAAM,CAAC,KAAe,EAAE,QAAsD,IAAS,CAAC;IAExF,+FAA+F;IAC/F,IAAI,YAAY;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,aAAa,CACX,KAAe,EACf,OAAiB,EACjB,KAAiB,EACjB,MAAmB,EACnB,QAAqC;QAErC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,YAAY,CACV,KAAe,EACf,OAAiB,EACjB,KAAgB,EAChB,SAAyB,EACzB,MAAmB,EACnB,QAAqC,EACrC,MAAsB;QAEtB,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["/**\n * The contract the shared packages actually depend on, rather than the Fabric implementation.\n *\n * `@ng-native/components` is meant to be renderer-agnostic: it describes a view and a press,\n * and leaves the drawing to a host. In practice they were coupled to Fabric twice over - `inject(Engine)`, which is a concrete\n * class whose constructor wants a `FabricUIManager`, and `EngineNode`, which is a Fabric tree\n * node. A web prototype could only satisfy both by casting a hand-written object through\n * `as unknown as Engine`, which typechecks and tells you nothing.\n *\n * So this is the seam, measured rather than guessed: every `engine.*` call and every `node.*`\n * field those three packages actually make, and nothing else. `Engine` implements `HostEngine` and\n * `EngineNode` extends `HostNode`, so nothing about the native path changes; a browser host\n * implements the same two and needs no cast.\n *\n * It lives in this package because everything already imports from here and importing it costs a\n * web build nothing - the Fabric bindings are reached lazily, through a global the browser does\n * not have. If that stops being true, moving these two declarations to a package of their own is\n * mechanical: nothing here refers to Fabric.\n *\n * ## What is deliberately not in it\n *\n * `tagOf` and `shadowNodeOf` hand out Fabric's own handles, for Reanimated worklets and\n * react-native-gesture-handler. The three components that use them - `worklet-style`,\n * `worklet-scroll` and `native-gesture` - inject the concrete `Engine` and are native-only by\n * nature, which is the honest place to draw that line rather than pretending a browser could\n * answer.\n */\nimport type {\n DrivenProperty,\n EventFeed,\n ScrollAxis,\n ScrollDrive,\n ScrollRange,\n StaticTransform,\n} from './native-drive.ts';\nimport type { ResponderHandlers, WindowFrame } from './engine.ts';\n\n/** What a host node has to be able to answer. `EngineNode` and a DOM element both can. */\nexport interface HostNode {\n /** `element` or `text`, so a caller can tell a node from the words inside it. */\n readonly kind: string;\n /**\n * The props the host is holding for this node.\n *\n * A real bag rather than an accessor, because `ViewBase` deletes from it: an input that a\n * directive consumes has to stop being an attribute the host writes through.\n */\n props: Record<string, unknown>;\n /** The text, when `kind` is `text`. Empty otherwise. */\n text: string;\n children: readonly HostNode[];\n parent: HostNode | null;\n /**\n * Set by `claimHost` from every host primitive's constructor, so a host can tell an element a\n * component owns from a bare one somebody wrote by hand.\n *\n * Part of the contract rather than a Fabric detail, because the shared packages are what set\n * it: `ViewBase`'s constructor calls `claimHost` on every element it hosts. What a host does\n * with it is its own business - Fabric warns about an unclaimed primitive name, and a browser\n * has nothing to warn about.\n */\n claimed?: true;\n /**\n * The engine that made this node, when the host can say. A primitive reads it rather than\n * injecting `HostEngine`, because that lookup walks every element injector up to the root and\n * runs once per element on screen. A host whose nodes cannot carry it leaves it out, and the\n * primitive injects as before.\n */\n readonly host?: HostEngine;\n}\n\n/** Something that writes its node's props once it has its inputs. See `HostEngine.settle`. */\nexport interface Settling {\n settleProps(): void;\n}\n\n/**\n * What a host has to be able to do.\n *\n * An abstract class rather than an interface plus an `InjectionToken`, because Angular can inject\n * an abstract class directly and the alternative is two names for one thing.\n */\nexport abstract class HostEngine {\n /** Write a prop. `undefined` and `null` both clear it, which is how native says \"default\". */\n abstract setProp(node: HostNode, key: string, value: unknown): void;\n /** Listen for one of the host's own events. Returns its own teardown. */\n abstract setEventListener(\n node: HostNode,\n topLevelType: string,\n fn: (event: unknown) => void,\n ): () => void;\n /** Claim gestures on a node, through whatever arbitration the host has. */\n abstract setResponder(node: HostNode, handlers: ResponderHandlers): () => void;\n /** Where the node is on screen, asynchronously, because native answers on its own thread. */\n abstract measure(node: HostNode, into: (frame: WindowFrame) => void): void;\n /** Tell a native view to do something imperative: scroll, focus, blur. */\n abstract dispatchCommand(node: HostNode, name: string, args?: readonly unknown[]): void;\n /** Flush pending work. Native batches; a browser may not need to. */\n abstract commit(): boolean;\n /** The node holding focus, or null. Verified to still be attached before it is handed back. */\n abstract get focused(): HostNode | null;\n /** Turn whatever an app passed as an image source into what the host wants. */\n abstract resolveAsset(value: unknown): unknown;\n /** Turn a colour the app wrote into whatever the host draws with. */\n abstract color(value: unknown): unknown;\n\n /**\n * Call `target.settleProps()` before the next commit, once the element's static inputs are set.\n *\n * A host primitive writes its props from `ngOnChanges` and `ngOnInit`, which run in its parent\n * view's update pass. A parent that detaches itself before its first check never has one, so\n * without this its pressables would take no press and carry no role or `testID`, where on the\n * web a listener and an attribute are there from creation. The default settles in a microtask,\n * for a host with no commit of its own to hang it on.\n */\n settle(target: Settling): void {\n queueMicrotask(() => target.settleProps());\n }\n\n /**\n * Measure a node again from what native now holds for it, in a commit of its own. For a host\n * that measures when content changes, as a browser does, there is nothing to do.\n */\n remeasure(_node: HostNode): void {}\n\n /**\n * Take the user to a node: a text input gets the cursor, and anything else is brought on\n * screen. What a DOM element's own `focus()` does, for code that calls it on an element.\n */\n focus(_node: HostNode): void {}\n\n /**\n * Scroll whatever the node is in just far enough to show all of it; `visibleBottom` is where in\n * the window the visible area ends, when something such as the keyboard covers its bottom.\n */\n reveal(_node: HostNode, _options?: { visibleBottom?: number; margin?: number }): void {}\n\n /** Whether `driveByScroll` moves views on the native side. A host without it moves nothing. */\n get drivesScroll(): boolean {\n return false;\n }\n\n /**\n * Move `view` by `scroll`'s offset along `axis` on the native side, through `range`, keeping\n * `statics` in its transform. Null when the host cannot, and the caller moves the view itself.\n */\n driveByScroll(\n _view: HostNode,\n _scroll: HostNode,\n _axis: ScrollAxis,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n ): ScrollDrive | null {\n return null;\n }\n\n /**\n * Move `view` by a value `source`'s native events carry, such as a keyboard's height, on the\n * native side, plus `shift` when one is given, which `ScrollDrive.shift` changes. Null when the\n * host cannot. See `driveByScroll`.\n */\n driveByEvent(\n _view: HostNode,\n _source: HostNode,\n _feed: EventFeed,\n _property: DrivenProperty,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n _shift?: number | null,\n ): ScrollDrive | null {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AA4EA;;;;;GAKG;AACH,MAAM,OAAgB,UAAU;IAwB9B;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAgB;QACrB,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAe,IAAS,CAAC;IAEnC;;;;;OAKG;IACH,UAAU,CAAC,KAAe,EAAE,KAAsB,IAAS,CAAC;IAE5D;;;OAGG;IACH,KAAK,CAAC,KAAe,IAAS,CAAC;IAE/B;;;OAGG;IACH,MAAM,CAAC,KAAe,EAAE,QAAsD,IAAS,CAAC;IAExF,+FAA+F;IAC/F,IAAI,YAAY;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,aAAa,CACX,KAAe,EACf,OAAiB,EACjB,KAAiB,EACjB,MAAmB,EACnB,QAAqC;QAErC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,YAAY,CACV,KAAe,EACf,OAAiB,EACjB,KAAgB,EAChB,SAAyB,EACzB,MAAmB,EACnB,QAAqC,EACrC,MAAsB;QAEtB,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["/**\n * The contract the shared packages actually depend on, rather than the Fabric implementation.\n *\n * `@ng-native/components` is meant to be renderer-agnostic: it describes a view and a press,\n * and leaves the drawing to a host. In practice they were coupled to Fabric twice over - `inject(Engine)`, which is a concrete\n * class whose constructor wants a `FabricUIManager`, and `EngineNode`, which is a Fabric tree\n * node. A web prototype could only satisfy both by casting a hand-written object through\n * `as unknown as Engine`, which typechecks and tells you nothing.\n *\n * So this is the seam, measured rather than guessed: every `engine.*` call and every `node.*`\n * field those three packages actually make, and nothing else. `Engine` implements `HostEngine` and\n * `EngineNode` extends `HostNode`, so nothing about the native path changes; a browser host\n * implements the same two and needs no cast.\n *\n * It lives in this package because everything already imports from here and importing it costs a\n * web build nothing - the Fabric bindings are reached lazily, through a global the browser does\n * not have. If that stops being true, moving these two declarations to a package of their own is\n * mechanical: nothing here refers to Fabric.\n *\n * ## What is deliberately not in it\n *\n * `tagOf` and `shadowNodeOf` hand out Fabric's own handles, for Reanimated worklets and\n * react-native-gesture-handler. The three components that use them - `worklet-style`,\n * `worklet-scroll` and `native-gesture` - inject the concrete `Engine` and are native-only by\n * nature, which is the honest place to draw that line rather than pretending a browser could\n * answer.\n */\nimport type {\n DrivenProperty,\n EventFeed,\n ScrollAxis,\n ScrollDrive,\n ScrollRange,\n StaticTransform,\n} from './native-drive.ts';\nimport type { ResponderHandlers, WindowFrame } from './engine.ts';\n\n/** What a host node has to be able to answer. `EngineNode` and a DOM element both can. */\nexport interface HostNode {\n /** `element` or `text`, so a caller can tell a node from the words inside it. */\n readonly kind: string;\n /**\n * The props the host is holding for this node.\n *\n * A real bag rather than an accessor, because `ViewBase` deletes from it: an input that a\n * directive consumes has to stop being an attribute the host writes through.\n */\n props: Record<string, unknown>;\n /** The text, when `kind` is `text`. Empty otherwise. */\n text: string;\n children: readonly HostNode[];\n parent: HostNode | null;\n /**\n * Set by `claimHost` from every host primitive's constructor, so a host can tell an element a\n * component owns from a bare one somebody wrote by hand.\n *\n * Part of the contract rather than a Fabric detail, because the shared packages are what set\n * it: `ViewBase`'s constructor calls `claimHost` on every element it hosts. What a host does\n * with it is its own business - Fabric warns about an unclaimed primitive name, and a browser\n * has nothing to warn about.\n */\n claimed?: true;\n /**\n * The engine that made this node, when the host can say. A primitive reads it rather than\n * injecting `HostEngine`, because that lookup walks every element injector up to the root and\n * runs once per element on screen. A host whose nodes cannot carry it leaves it out, and the\n * primitive injects as before.\n */\n readonly host?: HostEngine;\n}\n\n/** Something that writes its node's props once it has its inputs. See `HostEngine.settle`. */\nexport interface Settling {\n settleProps(): void;\n}\n\n/**\n * What a host has to be able to do.\n *\n * An abstract class rather than an interface plus an `InjectionToken`, because Angular can inject\n * an abstract class directly and the alternative is two names for one thing.\n */\nexport abstract class HostEngine {\n /** Write a prop. `undefined` and `null` both clear it, which is how native says \"default\". */\n abstract setProp(node: HostNode, key: string, value: unknown): void;\n /** Listen for one of the host's own events. Returns its own teardown. */\n abstract setEventListener(\n node: HostNode,\n topLevelType: string,\n fn: (event: unknown) => void,\n ): () => void;\n /** Claim gestures on a node, through whatever arbitration the host has. */\n abstract setResponder(node: HostNode, handlers: ResponderHandlers): () => void;\n /** Where the node is on screen, asynchronously, because native answers on its own thread. */\n abstract measure(node: HostNode, into: (frame: WindowFrame) => void): void;\n /** Tell a native view to do something imperative: scroll, focus, blur. */\n abstract dispatchCommand(node: HostNode, name: string, args?: readonly unknown[]): void;\n /** Flush pending work. Native batches; a browser may not need to. */\n abstract commit(): boolean;\n /** The node holding focus, or null. Verified to still be attached before it is handed back. */\n abstract get focused(): HostNode | null;\n /** Turn whatever an app passed as an image source into what the host wants. */\n abstract resolveAsset(value: unknown): unknown;\n /** Turn a colour the app wrote into whatever the host draws with. */\n abstract color(value: unknown): unknown;\n\n /**\n * Call `target.settleProps()` before the next commit, once the element's static inputs are set.\n *\n * A host primitive writes its props from `ngOnChanges` and `ngOnInit`, which run in its parent\n * view's update pass. A parent that detaches itself before its first check never has one, so\n * without this its pressables would take no press and carry no role or `testID`, where on the\n * web a listener and an attribute are there from creation. The default settles in a microtask,\n * for a host with no commit of its own to hang it on.\n */\n settle(target: Settling): void {\n queueMicrotask(() => target.settleProps());\n }\n\n /**\n * Measure a node again from what native now holds for it, in a commit of its own. For a host\n * that measures when content changes, as a browser does, there is nothing to do.\n */\n remeasure(_node: HostNode): void {}\n\n /**\n * Style `node` as an element of the template `like` is written in, rather than of the one that\n * created it, or as its own again when `like` is null: the content view a scroll view makes for\n * its children, which the app styles through a class of its own and so from its own component\n * styles.\n */\n adoptScope(_node: HostNode, _like: HostNode | null): void {}\n\n /**\n * Take the user to a node: a text input gets the cursor, and anything else is brought on\n * screen. What a DOM element's own `focus()` does, for code that calls it on an element.\n */\n focus(_node: HostNode): void {}\n\n /**\n * Scroll whatever the node is in just far enough to show all of it; `visibleBottom` is where in\n * the window the visible area ends, when something such as the keyboard covers its bottom.\n */\n reveal(_node: HostNode, _options?: { visibleBottom?: number; margin?: number }): void {}\n\n /** Whether `driveByScroll` moves views on the native side. A host without it moves nothing. */\n get drivesScroll(): boolean {\n return false;\n }\n\n /**\n * Move `view` by `scroll`'s offset along `axis` on the native side, through `range`, keeping\n * `statics` in its transform. Null when the host cannot, and the caller moves the view itself.\n */\n driveByScroll(\n _view: HostNode,\n _scroll: HostNode,\n _axis: ScrollAxis,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n ): ScrollDrive | null {\n return null;\n }\n\n /**\n * Move `view` by a value `source`'s native events carry, such as a keyboard's height, on the\n * native side, plus `shift` when one is given, which `ScrollDrive.shift` changes. Null when the\n * host cannot. See `driveByScroll`.\n */\n driveByEvent(\n _view: HostNode,\n _source: HostNode,\n _feed: EventFeed,\n _property: DrivenProperty,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n _shift?: number | null,\n ): ScrollDrive | null {\n return null;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  export { StyleResolver, matches, styleSheetOf, type AttributeTest, type Combinator, type Compound, type Conditions, type DeferredDeclaration, type GradientTemplate, type HslChannel, type MediaCondition, type NthTest, type RuleIndex, type StyleCache, type StyleRule, type StyleSheet, type StyleTarget, type TokenKind, type TokenValue, } from './css.ts';
14
14
  export { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';
15
15
  export { faceName } from './font-faces.ts';
16
- export { Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, registerHoist, registerPlatformComponents, registerViewName, type PlatformViewName, viewNameOf, type EngineNode, type EngineOptions, type EngineStats, type FabricNode, type FabricNodeSet, type FabricUIManager, type HoistOptions, type NativeSyntheticEvent, type NodeKind, type ResponderEvent, type ResponderHandlers, type TouchPayload, type ViewNameNode, type WindowFrame, } from './engine.ts';
16
+ export { DIRECT_EVENTS, Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, registerHoist, registerPlatformComponents, registerViewName, type PlatformViewName, type ViewNameOptions, viewNameOf, type EngineNode, type EngineOptions, type EngineStats, type FabricNode, type FabricNodeSet, type FabricUIManager, type HoistOptions, type NativeSyntheticEvent, type NodeKind, type ResponderEvent, type ResponderHandlers, type TouchPayload, type ViewNameNode, type WindowFrame, } from './engine.ts';
17
17
  export { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';
18
18
  export { HostEngine, type HostNode, type Settling } from './host.ts';
19
19
  export { getFabricUIManager } from './fabric.ts';
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  export { StyleResolver, matches, styleSheetOf, } from "./css.js";
14
14
  export { EngineIntersectionObserver, installDeferTriggers } from "./defer-triggers.js";
15
15
  export { faceName } from "./font-faces.js";
16
- export { Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, registerHoist, registerPlatformComponents, registerViewName, viewNameOf, } from "./engine.js";
16
+ export { DIRECT_EVENTS, Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, registerHoist, registerPlatformComponents, registerViewName, viewNameOf, } from "./engine.js";
17
17
  export { reportUnboundFormsInput } from "./forms-inputs.js";
18
18
  export { HostEngine } from "./host.js";
19
19
  export { getFabricUIManager } from "./fabric.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,YAAY,GAiBb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,MAAM,EACN,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAEhB,UAAU,GAeX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,uBAAuB,EAAsB,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAgC,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,WAAW,GAQZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,MAAM,EACN,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,GAMN,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * The engine's public surface.\n *\n * Nothing here imports Angular, and nothing here may start to: the engine stays\n * framework-agnostic (see docs/ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in\n * the loop and a second adapter possible later.\n *\n * The CSS runtime lives here rather than in a package of its own because the cascade is driven\n * from the commit walk - the resolver runs per node, per tick - and because it is as\n * framework-agnostic as the rest of the engine. The build-time half of CSS is a different\n * animal and lives in `@ng-native/metro`.\n */\nexport {\n StyleResolver,\n matches,\n styleSheetOf,\n type AttributeTest,\n type Combinator,\n type Compound,\n type Conditions,\n type DeferredDeclaration,\n type GradientTemplate,\n type HslChannel,\n type MediaCondition,\n type NthTest,\n type RuleIndex,\n type StyleCache,\n type StyleRule,\n type StyleSheet,\n type StyleTarget,\n type TokenKind,\n type TokenValue,\n} from './css.ts';\nexport { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport { faceName } from './font-faces.ts';\nexport {\n Engine,\n SyntheticEvent,\n claimHost,\n declareNativeProps,\n fontsRegistered,\n markComponentHost,\n nativePlatform,\n onFontsRegistered,\n registerHoist,\n registerPlatformComponents,\n registerViewName,\n type PlatformViewName,\n viewNameOf,\n type EngineNode,\n type EngineOptions,\n type EngineStats,\n type FabricNode,\n type FabricNodeSet,\n type FabricUIManager,\n type HoistOptions,\n type NativeSyntheticEvent,\n type NodeKind,\n type ResponderEvent,\n type ResponderHandlers,\n type TouchPayload,\n type ViewNameNode,\n type WindowFrame,\n} from './engine.ts';\nexport { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';\nexport { HostEngine, type HostNode, type Settling } from './host.ts';\nexport { getFabricUIManager } from './fabric.ts';\nexport {\n pinnedRange,\n type DrivenProperty,\n type EventFeed,\n type NativeAnimated,\n type ScrollAxis,\n type ScrollDrive,\n type ScrollRange,\n type StaticTransform,\n} from './native-drive.ts';\nexport {\n bezier,\n interpolate,\n redirect,\n sample,\n settled,\n step,\n tracksOf,\n tween,\n type AnimationSpec,\n type Keyframe,\n type RunningAnimation,\n type Transition,\n type TransitionSpec,\n} from './transition.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,YAAY,GAiBb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAGhB,UAAU,GAeX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,uBAAuB,EAAsB,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAgC,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,WAAW,GAQZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,MAAM,EACN,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,GAMN,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * The engine's public surface.\n *\n * Nothing here imports Angular, and nothing here may start to: the engine stays\n * framework-agnostic (see docs/ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in\n * the loop and a second adapter possible later.\n *\n * The CSS runtime lives here rather than in a package of its own because the cascade is driven\n * from the commit walk - the resolver runs per node, per tick - and because it is as\n * framework-agnostic as the rest of the engine. The build-time half of CSS is a different\n * animal and lives in `@ng-native/metro`.\n */\nexport {\n StyleResolver,\n matches,\n styleSheetOf,\n type AttributeTest,\n type Combinator,\n type Compound,\n type Conditions,\n type DeferredDeclaration,\n type GradientTemplate,\n type HslChannel,\n type MediaCondition,\n type NthTest,\n type RuleIndex,\n type StyleCache,\n type StyleRule,\n type StyleSheet,\n type StyleTarget,\n type TokenKind,\n type TokenValue,\n} from './css.ts';\nexport { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport { faceName } from './font-faces.ts';\nexport {\n DIRECT_EVENTS,\n Engine,\n SyntheticEvent,\n claimHost,\n declareNativeProps,\n fontsRegistered,\n markComponentHost,\n nativePlatform,\n onFontsRegistered,\n registerHoist,\n registerPlatformComponents,\n registerViewName,\n type PlatformViewName,\n type ViewNameOptions,\n viewNameOf,\n type EngineNode,\n type EngineOptions,\n type EngineStats,\n type FabricNode,\n type FabricNodeSet,\n type FabricUIManager,\n type HoistOptions,\n type NativeSyntheticEvent,\n type NodeKind,\n type ResponderEvent,\n type ResponderHandlers,\n type TouchPayload,\n type ViewNameNode,\n type WindowFrame,\n} from './engine.ts';\nexport { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';\nexport { HostEngine, type HostNode, type Settling } from './host.ts';\nexport { getFabricUIManager } from './fabric.ts';\nexport {\n pinnedRange,\n type DrivenProperty,\n type EventFeed,\n type NativeAnimated,\n type ScrollAxis,\n type ScrollDrive,\n type ScrollRange,\n type StaticTransform,\n} from './native-drive.ts';\nexport {\n bezier,\n interpolate,\n redirect,\n sample,\n settled,\n step,\n tracksOf,\n tween,\n type AnimationSpec,\n type Keyframe,\n type RunningAnimation,\n type Transition,\n type TransitionSpec,\n} from './transition.ts';\n"]}
@@ -0,0 +1,6 @@
1
+ import type { TokenValue } from './css.ts';
2
+ /**
3
+ * A value with a `var()` inside it, or a `color-mix()`, as a token made of others; undefined if it
4
+ * is no such shape.
5
+ */
6
+ export declare function derivedToken(text: string): TokenValue | undefined;
@@ -0,0 +1,487 @@
1
+ /**
2
+ * A custom property set on an element to a value with a `var()` inside it,
3
+ * `[style.--size]="'calc(var(--gap) * 2)'"`, read into the form the same text in a stylesheet is
4
+ * compiled to, which the cascade then works out where it is set, as it does that one
5
+ * (`resolveAliases` in css.ts).
6
+ *
7
+ * The shapes are the build-time conversion's and no more (`deferCalcToken`, `deferHslToken`,
8
+ * `deferChannelsToken` and `deferMixToken` in `@ng-native/metro/css/compile.cjs`): `calc()`,
9
+ * `min()` or `max()` of numbers, lengths in `px` or `rem`, angles, times and `var()`, with + - * /
10
+ * and brackets; an `hsl()` with a `var()` for a channel; `rgb()` or `hsl()` of one channels token,
11
+ * with an alpha written or tokened; and a `color-mix()` of any of these colours, tokens and
12
+ * colours written out. Anything else is undefined, as a stylesheet refuses it.
13
+ */
14
+ import { MIX_SPACES } from "./color-mix.js";
15
+ import { tokenFromValue } from "./inline-token.js";
16
+ const SPACE = /\s*/y;
17
+ const FUNCTION = /([a-z][a-z-]*)\(\s*/iy;
18
+ const VAR = /var\(\s*(--[\w-]+)\s*/iy;
19
+ const LITERAL = /([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(%|[a-z]+)?/iy;
20
+ const WORD = /[^\s,/()]+/y;
21
+ /** A fallback that is a `var()` or arithmetic, rather than a value written out. */
22
+ const NESTED = /^\s*(var|calc|min|max)\(/i;
23
+ /** What one of each unit counts as: points, degrees, milliseconds, or itself. */
24
+ const PER_UNIT = {
25
+ '': 1,
26
+ px: 1,
27
+ rem: 16,
28
+ deg: 1,
29
+ grad: 0.9,
30
+ rad: 180 / Math.PI,
31
+ turn: 360,
32
+ ms: 1,
33
+ s: 1000,
34
+ };
35
+ const PERCENTAGE = /([+-]?(?:\d+\.?\d*|\.\d+))%/y;
36
+ const HUE_UNITS = new Set(['', 'deg', 'grad', 'rad', 'turn']);
37
+ const HUE_METHODS = new Set(['shorter', 'longer', 'increasing', 'decreasing']);
38
+ const MATH = new Set(['calc', 'min', 'max']);
39
+ const SPACES = {
40
+ rgb: 'rgb',
41
+ rgba: 'rgb',
42
+ hsl: 'hsl',
43
+ hsla: 'hsl',
44
+ };
45
+ /** Thrown where the text stops being one of the shapes, and caught by `whole`. */
46
+ const UNREADABLE = new Error('not a token made of others');
47
+ const fail = () => {
48
+ throw UNREADABLE;
49
+ };
50
+ function match(cursor, pattern) {
51
+ pattern.lastIndex = cursor.at;
52
+ const found = pattern.exec(cursor.text);
53
+ if (found)
54
+ cursor.at = pattern.lastIndex;
55
+ return found;
56
+ }
57
+ /** Whether the next character, past any space, is `char`; taken, with the space after it, if so. */
58
+ function take(cursor, char) {
59
+ match(cursor, SPACE);
60
+ if (cursor.text[cursor.at] !== char)
61
+ return false;
62
+ cursor.at++;
63
+ match(cursor, SPACE);
64
+ return true;
65
+ }
66
+ function expect(cursor, char) {
67
+ if (!take(cursor, char))
68
+ fail();
69
+ }
70
+ /** The whole text as `read` reads it, or undefined when it is not that shape. */
71
+ function whole(text, read) {
72
+ const cursor = { text, at: 0 };
73
+ try {
74
+ const value = read(cursor);
75
+ match(cursor, SPACE);
76
+ return cursor.at === text.length ? value : undefined;
77
+ }
78
+ catch (error) {
79
+ if (error === UNREADABLE)
80
+ return undefined;
81
+ throw error;
82
+ }
83
+ }
84
+ /** A `var()` from its name on, to the bracket that closes it. */
85
+ function reference(cursor, name) {
86
+ if (take(cursor, ')'))
87
+ return { reference: name };
88
+ expect(cursor, ',');
89
+ const start = cursor.at;
90
+ for (let depth = 0; cursor.at < cursor.text.length; cursor.at++) {
91
+ const char = cursor.text[cursor.at];
92
+ if (char === '(')
93
+ depth++;
94
+ else if (char === ')' && depth-- === 0)
95
+ break;
96
+ }
97
+ const fallback = cursor.text.slice(start, cursor.at);
98
+ expect(cursor, ')');
99
+ return { reference: name, fallback };
100
+ }
101
+ /** A fallback's token. One that is another `var()` is refused, as the compiler refuses it. */
102
+ function fallbackToken(text) {
103
+ const token = text === undefined ? undefined : tokenFromValue(text);
104
+ return token?.alias ? fail() : token;
105
+ }
106
+ /** A number, length, angle or time, as the number its slot counts in. */
107
+ function literal(cursor, units) {
108
+ const found = match(cursor, LITERAL) ?? fail();
109
+ return Number(found[1]) * (units((found[2] ?? '').toLowerCase()) ?? fail());
110
+ }
111
+ /** A number slot takes a percentage as a fraction, which a length slot cannot work out. */
112
+ const unitsOf = (kind) => (unit) => unit === '%' ? (kind === 'number' ? 0.01 : undefined) : PER_UNIT[unit];
113
+ function sum(cursor, kind) {
114
+ let value = product(cursor, kind);
115
+ for (let op; (op = ['+', '-'].find((char) => take(cursor, char)));) {
116
+ value = [op, value, product(cursor, kind)];
117
+ }
118
+ return value;
119
+ }
120
+ function product(cursor, kind) {
121
+ let value = factor(cursor, kind);
122
+ for (let op; (op = ['*', '/'].find((char) => take(cursor, char)));) {
123
+ value = [op, value, factor(cursor, kind)];
124
+ }
125
+ return value;
126
+ }
127
+ function factor(cursor, kind) {
128
+ if (take(cursor, '(')) {
129
+ const value = sum(cursor, kind);
130
+ expect(cursor, ')');
131
+ return value;
132
+ }
133
+ const name = match(cursor, VAR)?.[1];
134
+ if (name) {
135
+ const value = leafFallback(reference(cursor, name).fallback, kind);
136
+ return value === undefined ? { reference: name } : { reference: name, fallback: value };
137
+ }
138
+ const fn = match(cursor, FUNCTION)?.[1];
139
+ if (fn)
140
+ return math(cursor, fn, kind);
141
+ const value = literal(cursor, unitsOf(kind));
142
+ // A percentage, kept apart from a bare number, as `literal` in the compiler keeps it.
143
+ return cursor.text[cursor.at - 1] === '%' ? { percentage: value } : value;
144
+ }
145
+ /**
146
+ * A `var()` fallback in arithmetic, as its slot reads it: another `var()` or arithmetic of its
147
+ * own, read as a tree, `var(--a, calc(var(--gap) * 2))`, as `leaf` in the compiler reads it; a
148
+ * length in points, `rem` included as the compiler counts it; or a bare number.
149
+ */
150
+ function leafFallback(text, kind) {
151
+ if (text !== undefined && NESTED.test(text)) {
152
+ return whole(text.trim(), (cursor) => factor(cursor, kind)) ?? fail();
153
+ }
154
+ const token = fallbackToken(text);
155
+ if (kind !== 'length')
156
+ return numberFallback(token, kind);
157
+ if (typeof token?.length === 'number')
158
+ return token.length;
159
+ const rem = text === undefined ? undefined : /^\s*(-?\d*\.?\d+)rem\s*$/i.exec(text);
160
+ return rem ? Number(rem[1]) * PER_UNIT['rem'] : undefined;
161
+ }
162
+ /** A fallback where a number is wanted: a percentage kept apart from a bare number. */
163
+ function numberFallback(token, kind) {
164
+ const percentage = kind === 'number' && typeof token?.length === 'string' && token.number !== undefined;
165
+ return percentage ? { percentage: token.number } : token?.number;
166
+ }
167
+ /** The rest of a `calc()`, or of a `min()` or `max()` of sums, once its name is read. */
168
+ function math(cursor, fn, kind) {
169
+ const name = fn.toLowerCase();
170
+ if (!MATH.has(name))
171
+ fail();
172
+ const sides = [sum(cursor, kind)];
173
+ while (name !== 'calc' && take(cursor, ','))
174
+ sides.push(sum(cursor, kind));
175
+ expect(cursor, ')');
176
+ return sides.reduce((a, b) => [name, a, b]);
177
+ }
178
+ /** Arithmetic of tokens, read as a length and as a number, whichever each reading makes of it. */
179
+ function calcToken(text) {
180
+ const markers = ['length', 'number'].flatMap((kind) => {
181
+ const expression = whole(text, (cursor) => math(cursor, (match(cursor, FUNCTION) ?? fail())[1], kind));
182
+ return expression === undefined ? [] : [{ expression, kind }];
183
+ });
184
+ return markers.length ? { deferredCalc: markers } : undefined;
185
+ }
186
+ /**
187
+ * A colour function's arguments, with the commas and the slash between them gone, and whether
188
+ * commas were what separated them.
189
+ */
190
+ function colourArguments(cursor) {
191
+ const space = SPACES[(match(cursor, FUNCTION) ?? fail())[1].toLowerCase()] ?? fail();
192
+ const args = [];
193
+ let legacy = false;
194
+ while (!take(cursor, ')')) {
195
+ if (take(cursor, ','))
196
+ legacy = true;
197
+ else if (!take(cursor, '/')) {
198
+ const name = match(cursor, VAR)?.[1];
199
+ args.push(name ? reference(cursor, name) : { word: (match(cursor, WORD) ?? fail())[0] });
200
+ }
201
+ }
202
+ return { space, args, legacy };
203
+ }
204
+ /** What each argument of an `hsl()` is: a hue, a saturation and a lightness, then an alpha. */
205
+ const HSL_SLOTS = ['hue', 'percentage', 'percentage', 'alpha'];
206
+ /**
207
+ * A written `hsl()` channel as its slot reads it, as `hslLiteral` in the compiler does: a hue in
208
+ * degrees; a saturation or a lightness as a fraction, from a percentage or, in the space syntax
209
+ * only, a bare number of percent; an alpha as a number or a fraction.
210
+ */
211
+ function hslLiteral(text, slot, legacy) {
212
+ const perUnit = (unit) => {
213
+ if (slot === 'hue')
214
+ return HUE_UNITS.has(unit) ? PER_UNIT[unit] : undefined;
215
+ if (unit === '%')
216
+ return 0.01;
217
+ if (unit !== '')
218
+ return undefined;
219
+ return slot === 'alpha' ? 1 : legacy ? undefined : 0.01;
220
+ };
221
+ return whole(text, (cursor) => literal(cursor, perUnit));
222
+ }
223
+ function hslChannel(arg, index, legacy) {
224
+ const slot = HSL_SLOTS[index];
225
+ if ('word' in arg)
226
+ return hslLiteral(arg.word, slot, legacy) ?? fail();
227
+ const fallback = arg.fallback === undefined ? undefined : hslLiteral(arg.fallback, slot, legacy);
228
+ return fallback === undefined
229
+ ? { reference: arg.reference }
230
+ : { reference: arg.reference, fallback };
231
+ }
232
+ /** A channels colour's alpha: none, written as a number or a percentage, or a token of its own. */
233
+ function alphaOf(arg) {
234
+ if (arg === undefined)
235
+ return undefined;
236
+ if ('word' in arg) {
237
+ return whole(arg.word, (cursor) => literal(cursor, unitsOf('number'))) ?? fail();
238
+ }
239
+ const fallback = fallbackToken(arg.fallback)?.number;
240
+ return fallback === undefined
241
+ ? { reference: arg.reference }
242
+ : { reference: arg.reference, fallback };
243
+ }
244
+ /** `rgba(var(--channels), <alpha>)`, as `deferChannelsToken` reads it. */
245
+ function channelsToken(channels, alpha, space) {
246
+ const token = fallbackToken(channels.fallback);
247
+ const fallback = space === 'hsl' ? token?.hslChannels : token?.channels;
248
+ const opacity = alphaOf(alpha);
249
+ return {
250
+ deferredColour: {
251
+ channels: { reference: channels.reference, ...(fallback ? { fallback } : {}), space },
252
+ ...(opacity === undefined ? {} : { alpha: opacity }),
253
+ },
254
+ };
255
+ }
256
+ /** `hsl(var(--h) 100% 50%)` and the like, as `deferHslToken` reads it. */
257
+ function hslToken(args, legacy) {
258
+ if (args.length !== 3 && args.length !== 4)
259
+ fail();
260
+ const [h, s, l, alpha] = args.map((arg, index) => hslChannel(arg, index, legacy));
261
+ const legacyFlag = legacy ? { legacy: true } : {};
262
+ return { hsl: { h: h, s: s, l: l, ...(alpha === undefined ? {} : { alpha }), ...legacyFlag } };
263
+ }
264
+ function colourToken(text) {
265
+ return whole(text, (cursor) => {
266
+ const { space, args, legacy } = colourArguments(cursor);
267
+ const [channels, alpha, ...rest] = args;
268
+ if (channels && 'reference' in channels && !rest.length) {
269
+ return channelsToken(channels, alpha, space);
270
+ }
271
+ return space === 'hsl' ? hslToken(args, legacy) : fail();
272
+ });
273
+ }
274
+ /** The index just past the bracket that closes the function the cursor is inside. */
275
+ function closing(cursor) {
276
+ for (let at = cursor.at, depth = 0; at < cursor.text.length; at++) {
277
+ const char = cursor.text[at];
278
+ if (char === '(')
279
+ depth++;
280
+ else if (char === ')' && depth-- === 0)
281
+ return at + 1;
282
+ }
283
+ return fail();
284
+ }
285
+ /**
286
+ * A `var()` in a colour, as `reference` in the compiler's colour-expression.cjs reads it: the
287
+ * tokens its `var()` fallbacks name, tried in turn, then a colour, or one made of other tokens.
288
+ */
289
+ function referenceColour({ reference: name, fallback }) {
290
+ const alternatives = [];
291
+ let rest = fallback?.trim();
292
+ for (let next; rest && (next = whole(rest, nextReference)); rest = next.fallback?.trim()) {
293
+ alternatives.push(next.reference);
294
+ }
295
+ return {
296
+ reference: name,
297
+ ...(alternatives.length ? { alternatives } : {}),
298
+ ...(rest ? colourFallback(rest) : {}),
299
+ };
300
+ }
301
+ /** The colour a `var()` falls back to, or one made of other tokens; refused when it is neither. */
302
+ function colourFallback(text) {
303
+ const token = tokenFromValue(text);
304
+ if (token?.hsl || token?.deferredColour)
305
+ return { fallbackToken: token };
306
+ return { fallback: token?.color ?? fail() };
307
+ }
308
+ const nextReference = (cursor) => reference(cursor, (match(cursor, VAR) ?? fail())[1]);
309
+ /**
310
+ * One colour in a `color-mix()`: a `var()`, another mix, an `rgb()` or `hsl()` of tokens, or a
311
+ * colour written out, which is kept as written for the mix to read.
312
+ */
313
+ function mixColour(cursor) {
314
+ const name = match(cursor, VAR)?.[1];
315
+ if (name)
316
+ return referenceColour(reference(cursor, name));
317
+ const start = cursor.at;
318
+ const fn = match(cursor, FUNCTION)?.[1]?.toLowerCase();
319
+ if (fn === 'color-mix')
320
+ return mixExpression(cursor);
321
+ if (!fn)
322
+ return { color: (match(cursor, WORD) ?? fail())[0] };
323
+ if (RELATIVE[fn] && takeWord(cursor, 'from'))
324
+ return relativeExpression(cursor, RELATIVE[fn]);
325
+ cursor.at = closing(cursor);
326
+ return functionColour(cursor.text.slice(start, cursor.at));
327
+ }
328
+ /** A colour function in a mix: of tokens, as a token of it is read, or written out, as it is. */
329
+ function functionColour(text) {
330
+ if (!text.includes('var('))
331
+ return { color: text };
332
+ const token = colourToken(text);
333
+ return token?.hsl ? { hsl: token.hsl } : (token?.deferredColour ?? fail());
334
+ }
335
+ /** One side of a mix: its colour, and the percentage written before or after it, if one is. */
336
+ function mixSide(cursor) {
337
+ const percentage = () => {
338
+ const found = match(cursor, PERCENTAGE);
339
+ match(cursor, SPACE);
340
+ return found ? Number(found[1]) : undefined;
341
+ };
342
+ const before = percentage();
343
+ const colour = mixColour(cursor);
344
+ match(cursor, SPACE);
345
+ const after = before === undefined ? percentage() : undefined;
346
+ const written = before ?? after;
347
+ return written === undefined ? { colour } : { colour, percentage: written };
348
+ }
349
+ /**
350
+ * The rest of a `color-mix(in <space> [<method> hue], <colour> [p%], <colour> [q%])` once its name
351
+ * is read, as `mixExpression` in the compiler reads it.
352
+ */
353
+ function mixExpression(cursor) {
354
+ const words = [];
355
+ while (!take(cursor, ',')) {
356
+ words.push((match(cursor, WORD) ?? fail())[0].toLowerCase());
357
+ match(cursor, SPACE);
358
+ }
359
+ const [keyword, space, method, hue, ...rest] = words;
360
+ if (keyword !== 'in' || !MIX_SPACES.has(space) || rest.length)
361
+ fail();
362
+ if (method !== undefined && (hue !== 'hue' || !HUE_METHODS.has(method)))
363
+ fail();
364
+ const a = mixSide(cursor);
365
+ expect(cursor, ',');
366
+ const b = mixSide(cursor);
367
+ expect(cursor, ')');
368
+ return {
369
+ mix: {
370
+ space: space,
371
+ ...(method ? { hue: method } : {}),
372
+ a: a.colour,
373
+ ...(a.percentage === undefined ? {} : { aPercentage: a.percentage }),
374
+ b: b.colour,
375
+ ...(b.percentage === undefined ? {} : { bPercentage: b.percentage }),
376
+ },
377
+ };
378
+ }
379
+ /** A whole `color-mix()`, worked out where it is set, as `deferMixToken` in the compiler reads it. */
380
+ function mixToken(text) {
381
+ const deferredColour = whole(text, (cursor) => {
382
+ const colour = mixColour(cursor);
383
+ return 'mix' in colour || 'relative' in colour ? colour : fail();
384
+ });
385
+ return deferredColour && { deferredColour };
386
+ }
387
+ /** Each relative colour function, as `RELATIVE` in the compiler's colour-expression.cjs. */
388
+ const RELATIVE = {
389
+ rgb: { space: 'srgb', keywords: ['r', 'g', 'b'], full: [255, 255, 255] },
390
+ rgba: { space: 'srgb', keywords: ['r', 'g', 'b'], full: [255, 255, 255] },
391
+ hsl: { space: 'hsl', keywords: ['h', 's', 'l'], full: [null, 100, 100] },
392
+ hsla: { space: 'hsl', keywords: ['h', 's', 'l'], full: [null, 100, 100] },
393
+ hwb: { space: 'hwb', keywords: ['h', 'w', 'b'], full: [null, 100, 100] },
394
+ lab: { space: 'lab', keywords: ['l', 'a', 'b'], full: [100, 125, 125] },
395
+ lch: { space: 'lch', keywords: ['l', 'c', 'h'], full: [100, 150, null] },
396
+ oklab: { space: 'oklab', keywords: ['l', 'a', 'b'], full: [1, 0.4, 0.4] },
397
+ oklch: { space: 'oklch', keywords: ['l', 'c', 'h'], full: [1, 0.4, null] },
398
+ };
399
+ /** Whether the next word is `word`, in any case; taken, with the space after it, if so. */
400
+ function takeWord(cursor, word) {
401
+ const start = cursor.at;
402
+ if (match(cursor, WORD)?.[0].toLowerCase() === word) {
403
+ match(cursor, SPACE);
404
+ return true;
405
+ }
406
+ cursor.at = start;
407
+ return false;
408
+ }
409
+ /**
410
+ * The rest of `<space>(from <colour> <channel> <channel> <channel> [/ <alpha>])` once `from` is
411
+ * read, as `relativeExpression` in the compiler reads it.
412
+ */
413
+ function relativeExpression(cursor, { space, keywords, full }) {
414
+ const from = mixColour(cursor);
415
+ const names = [...keywords, 'alpha'];
416
+ const channels = [0, 1, 2].map((index) => {
417
+ match(cursor, SPACE);
418
+ return relativeChannel(cursor, names, full[index]);
419
+ });
420
+ const alpha = take(cursor, '/') ? relativeChannel(cursor, names, 1) : undefined;
421
+ expect(cursor, ')');
422
+ return { relative: { space, from, channels, ...(alpha === undefined ? {} : { alpha }) } };
423
+ }
424
+ /**
425
+ * One channel: a `calc()` of numbers and keywords, a keyword, a number, a percentage of the
426
+ * channel's range, or an angle for a hue.
427
+ */
428
+ function relativeChannel(cursor, keywords, full) {
429
+ const fn = match(cursor, FUNCTION)?.[1]?.toLowerCase();
430
+ if (fn) {
431
+ const value = fn === 'calc' ? channelSum(cursor, keywords) : fail();
432
+ expect(cursor, ')');
433
+ return value;
434
+ }
435
+ const found = match(cursor, LITERAL);
436
+ if (!found)
437
+ return channelOperand(cursor, keywords);
438
+ const value = Number(found[1]) * channelScale((found[2] ?? '').toLowerCase(), full);
439
+ return Number.isFinite(value) ? value : fail();
440
+ }
441
+ /** What one of a written channel's unit is: a share of its range, degrees of a hue, or itself. */
442
+ function channelScale(unit, full) {
443
+ if (unit === '%')
444
+ return full === null ? NaN : full / 100;
445
+ if (full !== null)
446
+ return unit ? NaN : 1;
447
+ return HUE_UNITS.has(unit) ? PER_UNIT[unit] : NaN;
448
+ }
449
+ /** `a + b - c`, each side a product of numbers, keywords and bracketed sums. */
450
+ function channelSum(cursor, keywords) {
451
+ let value = channelProduct(cursor, keywords);
452
+ for (let op; (op = ['+', '-'].find((char) => take(cursor, char)));) {
453
+ value = [op, value, channelProduct(cursor, keywords)];
454
+ }
455
+ return value;
456
+ }
457
+ function channelProduct(cursor, keywords) {
458
+ let value = channelFactor(cursor, keywords);
459
+ for (let op; (op = ['*', '/'].find((char) => take(cursor, char)));) {
460
+ value = [op, value, channelFactor(cursor, keywords)];
461
+ }
462
+ return value;
463
+ }
464
+ function channelFactor(cursor, keywords) {
465
+ if (take(cursor, '(')) {
466
+ const value = channelSum(cursor, keywords);
467
+ expect(cursor, ')');
468
+ return value;
469
+ }
470
+ const found = match(cursor, LITERAL);
471
+ // A percentage or an angle stands alone: inside arithmetic the keywords are numbers.
472
+ if (found)
473
+ return found[2] ? fail() : Number(found[1]);
474
+ return channelOperand(cursor, keywords);
475
+ }
476
+ function channelOperand(cursor, keywords) {
477
+ const word = (match(cursor, WORD) ?? fail())[0];
478
+ return keywords.includes(word) ? word : fail();
479
+ }
480
+ /**
481
+ * A value with a `var()` inside it, or a `color-mix()`, as a token made of others; undefined if it
482
+ * is no such shape.
483
+ */
484
+ export function derivedToken(text) {
485
+ return calcToken(text) ?? colourToken(text) ?? mixToken(text);
486
+ }
487
+ //# sourceMappingURL=inline-derived-token.js.map