@zakkster/lite-ui-fx 1.3.0 → 1.4.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/CHANGELOG.md +49 -0
- package/UIFX-RECIPE-GUIDE.md +3 -2
- package/UIFXController.d.ts +13 -0
- package/UIFXController.js +29 -2
- package/UIFXRecipes.d.ts +77 -51
- package/UIFXRecipes.js +510 -275
- package/llms.txt +12 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ All notable changes to `@zakkster/lite-ui-fx` are documented here.
|
|
|
5
5
|
The format follows Keep a Changelog; this project adheres to Semantic
|
|
6
6
|
Versioning.
|
|
7
7
|
|
|
8
|
+
## [1.4.0] -- 2026-09-07
|
|
9
|
+
|
|
10
|
+
The theming pass (U3b), completing U3's third finding (U-06). One option
|
|
11
|
+
convention across all 50 recipes; `themeable` is true for the first time. Every
|
|
12
|
+
default renders byte-for-byte as 1.3.0. Reduced motion (`motionSafe`) stays a
|
|
13
|
+
later pass.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- U-06 (theming): all 50 recipes honour one reserved option set
|
|
18
|
+
`{ seed, colors, theme: { light, mid, dark }, text, font }`, resolved once in the
|
|
19
|
+
factory / `init`, never per frame. `theme` maps light/mid/dark onto each recipe's
|
|
20
|
+
accent/dim/surface roles; `colors` overrides positionally and wins over `theme`;
|
|
21
|
+
the array-palette recipes (Confetti, Firework, Aurora, RadioOrbit,
|
|
22
|
+
PasswordStrength, ReactionPicker) keep reading `colors` as their palette and are
|
|
23
|
+
seeded by `theme`. Hardcoded canvas labels (`'MAGNETIC'`, ...) became `text`,
|
|
24
|
+
resolved as `text ?? label ?? default`, so a mount that sets only the accessible
|
|
25
|
+
`label` drives the visible string (WCAG 2.5.3 label-in-name); fonts became the
|
|
26
|
+
`font` option. Every default is byte-identical to 1.3.0 (recording-context
|
|
27
|
+
draw-signature diff over all 50 recipes).
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `RecipeOptions` (UIFXRecipes.d.ts) plus the shared cold helpers `resolveTheme`,
|
|
32
|
+
`pickText`, `pickFont`, `rgbaOf`, `rgbTriplet`; `MountOptions` gains the five
|
|
33
|
+
reserved keys.
|
|
34
|
+
- `test/theme.test.mjs`: 70 boundary tests (themeable flags, every recipe recolours
|
|
35
|
+
under a theme, colors-wins-over-theme, the legacy `colors`/`color` aliases, the
|
|
36
|
+
fail-closed validator matrix, label-in-name).
|
|
37
|
+
- t3 torture gates each recipe under a THEMED mount as well as its default
|
|
38
|
+
(`t3-scan.mjs` adds `tgrad`/`tcdist`; `t3-frame-alloc.mjs` judges both), proving
|
|
39
|
+
palette resolution stayed cold; t2 gains a label-in-name assertion.
|
|
40
|
+
- `tools/palettes.mjs` (not shipped): audits the shipped label colours for APCA
|
|
41
|
+
0.1.9 contrast and emits example theme triples via `@zakkster/lite-hueforge`
|
|
42
|
+
(a dev-only tool, never a runtime dependency). Recorded in
|
|
43
|
+
`decisions/0002-recipe-options.md`.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Controller: `KNOWN_OPTIONS` admits `seed` / `colors` / `theme` / `text` / `font`,
|
|
48
|
+
each validated fail closed -- a partial or extra-key `theme`, a non-array
|
|
49
|
+
`colors`, a non-string `text` / `font`, or a non-finite `seed` throws; an unknown
|
|
50
|
+
key still returns a did-you-mean.
|
|
51
|
+
- `RECIPE_META.themeable` is `true` for all 50 recipes; `motionSafe` stays `false`.
|
|
52
|
+
- The recording `Ctx2DStub` logs `fillText` / `strokeText` with their text argument.
|
|
53
|
+
- `UIFX-RECIPE-GUIDE.md`: the per-frame performance rules retire the pre-U3
|
|
54
|
+
"`splice()` is fine for < 100 particles" advice (it contradicts the t3 gate) and
|
|
55
|
+
add a rule to resolve theming in the factory / `init`, never in `tick`.
|
|
56
|
+
|
|
8
57
|
## [1.3.0] -- 2026-09-07
|
|
9
58
|
|
|
10
59
|
The recipe sweep (U3), first pass: zero per-frame allocation across all 50
|
package/UIFX-RECIPE-GUIDE.md
CHANGED
|
@@ -181,8 +181,9 @@ if (state.focused) {
|
|
|
181
181
|
|
|
182
182
|
## Performance Rules
|
|
183
183
|
|
|
184
|
-
1. **Never allocate in `tick()`** -- pre-allocate TypedArrays in the factory closure or `init()`
|
|
185
|
-
2. **
|
|
184
|
+
1. **Never allocate in `tick()`** -- pre-allocate TypedArrays / fixed object pools in the factory closure or `init()`. This is not a guideline: the `t3-frame-alloc` torture tier FAILS any built-in recipe that assigns a fresh colour string, builds a gradient, or grows a pool on a hot frame (idle OR under interaction churn), and re-runs the same budget under a themed mount.
|
|
185
|
+
2. **No `push`/`splice` on the hot path** -- a particle pool is a fixed-size array (or `Float64Array` lanes) with a `live`/`life` flag; spawn scans for a dead slot, death clears the flag. `splice()` allocates and shifts elements; it is a cold-path tool (mount/destroy) only, never per frame. (The pre-U3 "splice is fine for < 100 particles" advice is retired -- it contradicts the gate.)
|
|
186
186
|
3. **Reset composite operation** -- if you set `ctx.globalCompositeOperation = 'screen'`, reset to `'source-over'` before returning
|
|
187
187
|
4. **Reset shadow** -- `ctx.shadowBlur = 0` after drawing glowing elements
|
|
188
188
|
5. **Use the `dt` parameter** -- all motion must be `value * dt`, not `value` per frame. This ensures consistent speed regardless of frame rate.
|
|
189
|
+
6. **Resolve theming in the factory / `init()`, never in `tick()`** -- read `options.theme` / `colors` / `text` / `font` ONCE at construction (the built-ins use the shared `resolveTheme` / `pickText` / `pickFont` helpers), producing const colour strings and any value-LUT the hot body then reads. Alpha varies via `ctx.globalAlpha` over a const colour; a `rgba(...,${x})` or `` `${color}` `` built per frame trips t3. Defaults must reproduce the pre-theming output byte-for-byte.
|
package/UIFXController.d.ts
CHANGED
|
@@ -59,6 +59,19 @@ export interface MountOptions {
|
|
|
59
59
|
checked?: boolean;
|
|
60
60
|
/** Disables the native element and sets state.disabled for recipes. */
|
|
61
61
|
disabled?: boolean;
|
|
62
|
+
// -- Reserved recipe theming options (decisions/0002). Validated fail-closed,
|
|
63
|
+
// then forwarded to the recipe factory. All optional; omitting them keeps
|
|
64
|
+
// the recipe's shipped look byte-for-byte.
|
|
65
|
+
/** Seed for a recipe's deterministic RNG. */
|
|
66
|
+
seed?: number;
|
|
67
|
+
/** Positional palette override; wins over `theme`. */
|
|
68
|
+
colors?: string[];
|
|
69
|
+
/** Named theme roles: light -> accent, mid -> dim/muted, dark -> surface. */
|
|
70
|
+
theme?: { light: string; mid: string; dark: string };
|
|
71
|
+
/** Visible canvas label; falls back to `label`, then the recipe default. */
|
|
72
|
+
text?: string;
|
|
73
|
+
/** Canvas font string; falls back to the recipe's historical font. */
|
|
74
|
+
font?: string;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
export interface UIFXInstance {
|
package/UIFXController.js
CHANGED
|
@@ -22,7 +22,7 @@ import { Ticker } from '@zakkster/lite-ticker';
|
|
|
22
22
|
|
|
23
23
|
// Three-place version sync: this constant, package.json "version", and the
|
|
24
24
|
// VERSION line in llms.txt must always match. /release keeps them locked.
|
|
25
|
-
export const VERSION = '1.
|
|
25
|
+
export const VERSION = '1.4.0';
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------
|
|
28
28
|
// SHARED TICKER (ref-counted, one RAF for all UI components)
|
|
@@ -84,7 +84,7 @@ function releaseSliderStyle() {
|
|
|
84
84
|
// ---------------------------------------------------------
|
|
85
85
|
|
|
86
86
|
const KNOWN_HOOKS = ['init', 'tick', 'onHover', 'onLeave', 'onClick', 'onToggle', 'onDrag', 'destroy'];
|
|
87
|
-
const KNOWN_OPTIONS = ['width', 'height', 'padding', 'label', 'value', 'checked', 'disabled'];
|
|
87
|
+
const KNOWN_OPTIONS = ['width', 'height', 'padding', 'label', 'value', 'checked', 'disabled', 'seed', 'colors', 'theme', 'text', 'font'];
|
|
88
88
|
|
|
89
89
|
// Levenshtein edit distance. Cold: only reached on the error path.
|
|
90
90
|
function _editDistance(a, b) {
|
|
@@ -206,6 +206,33 @@ export function mountUIFX(container, type, recipeFactory, options = {}) {
|
|
|
206
206
|
const padding = options.padding === undefined ? 40 : options.padding;
|
|
207
207
|
const label = options.label === undefined ? '' : options.label;
|
|
208
208
|
|
|
209
|
+
// Reserved theming options (decisions/0002): all optional, validated fail
|
|
210
|
+
// closed here, then forwarded to the recipe factory which resolves them in
|
|
211
|
+
// init. This is cold mount code -- closures/allocation are fine here.
|
|
212
|
+
const _theme = options.theme;
|
|
213
|
+
if (_theme !== undefined) {
|
|
214
|
+
if (_theme === null || typeof _theme !== 'object' ||
|
|
215
|
+
typeof _theme.light !== 'string' || typeof _theme.mid !== 'string' ||
|
|
216
|
+
typeof _theme.dark !== 'string' || Object.keys(_theme).length !== 3) {
|
|
217
|
+
throw new Error('mountUIFX: option "theme" must be { light, mid, dark } of color strings');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const _colors = options.colors;
|
|
221
|
+
if (_colors !== undefined &&
|
|
222
|
+
(!Array.isArray(_colors) || _colors.some((c) => typeof c !== 'string'))) {
|
|
223
|
+
throw new Error('mountUIFX: option "colors" must be an array of color strings');
|
|
224
|
+
}
|
|
225
|
+
if (options.text !== undefined && typeof options.text !== 'string') {
|
|
226
|
+
throw new Error('mountUIFX: option "text" must be a string');
|
|
227
|
+
}
|
|
228
|
+
if (options.font !== undefined && typeof options.font !== 'string') {
|
|
229
|
+
throw new Error('mountUIFX: option "font" must be a string');
|
|
230
|
+
}
|
|
231
|
+
if (options.seed !== undefined &&
|
|
232
|
+
(typeof options.seed !== 'number' || !Number.isFinite(options.seed))) {
|
|
233
|
+
throw new Error('mountUIFX: option "seed" must be a finite number');
|
|
234
|
+
}
|
|
235
|
+
|
|
209
236
|
// 3. recipeFactory
|
|
210
237
|
if (typeof recipeFactory !== 'function') {
|
|
211
238
|
throw new Error('mountUIFX: recipeFactory must be a function');
|
package/UIFXRecipes.d.ts
CHANGED
|
@@ -1,92 +1,118 @@
|
|
|
1
1
|
import type { UIFXRecipe, UIFXInstance, MountOptions } from './UIFXController';
|
|
2
2
|
|
|
3
3
|
// ===========================================================
|
|
4
|
-
// RECIPE FACTORIES (all 50)
|
|
4
|
+
// RECIPE OPTIONS + FACTORIES (all 50)
|
|
5
5
|
// ===========================================================
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The reserved theming options every recipe factory accepts (decisions/0002).
|
|
9
|
+
* All optional; omitting them reproduces the recipe's shipped look byte-for-byte.
|
|
10
|
+
*/
|
|
11
|
+
export interface RecipeOptions {
|
|
12
|
+
/** Seed for a recipe's deterministic RNG (particle bursts, jitter). */
|
|
13
|
+
seed?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Positional palette override; wins over `theme`. For most recipes it maps
|
|
16
|
+
* over the role order (accent, dim, surface, ...); for an array-palette recipe
|
|
17
|
+
* (Confetti, Firework, Aurora, RadioOrbit, PasswordStrength, ReactionPicker)
|
|
18
|
+
* it IS the palette.
|
|
19
|
+
*/
|
|
20
|
+
colors?: string[];
|
|
21
|
+
/** Named theme roles: `light` -> accent, `mid` -> dim/muted, `dark` -> surface. */
|
|
22
|
+
theme?: { light: string; mid: string; dark: string };
|
|
23
|
+
/**
|
|
24
|
+
* Visible canvas label. Falls back to the mount `label`, then the recipe's
|
|
25
|
+
* default string -- so setting only `label` keeps the visible text equal to the
|
|
26
|
+
* accessible name (WCAG 2.5.3 label-in-name). Ignored by recipes painting no text.
|
|
27
|
+
*/
|
|
28
|
+
text?: string;
|
|
29
|
+
/** Canvas font string; falls back to the recipe's historical font literal. */
|
|
30
|
+
font?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
7
33
|
// -- Vol.1: Toggles --
|
|
8
|
-
export declare function SwarmToggle(options?:
|
|
9
|
-
export declare function LiquidToggle(): UIFXRecipe;
|
|
10
|
-
export declare function NeonPulseToggle(): UIFXRecipe;
|
|
34
|
+
export declare function SwarmToggle(options?: RecipeOptions & { count?: number }): UIFXRecipe;
|
|
35
|
+
export declare function LiquidToggle(options?: RecipeOptions): UIFXRecipe;
|
|
36
|
+
export declare function NeonPulseToggle(options?: RecipeOptions): UIFXRecipe;
|
|
11
37
|
|
|
12
38
|
// -- Vol.1: Buttons --
|
|
13
|
-
export declare function MagneticButton(options?: { maxPull?: number }): UIFXRecipe;
|
|
14
|
-
export declare function ShatterButton(options?:
|
|
15
|
-
export declare function ConfettiButton(options?:
|
|
16
|
-
export declare function GlitchButton(options?:
|
|
39
|
+
export declare function MagneticButton(options?: RecipeOptions & { maxPull?: number }): UIFXRecipe;
|
|
40
|
+
export declare function ShatterButton(options?: RecipeOptions): UIFXRecipe;
|
|
41
|
+
export declare function ConfettiButton(options?: RecipeOptions): UIFXRecipe;
|
|
42
|
+
export declare function GlitchButton(options?: RecipeOptions): UIFXRecipe;
|
|
17
43
|
|
|
18
44
|
// -- Vol.1: Sliders --
|
|
19
|
-
export declare function SparkSlider(options?:
|
|
20
|
-
export declare function CosmicSlider(options?:
|
|
21
|
-
export declare function LaserSlider(): UIFXRecipe;
|
|
45
|
+
export declare function SparkSlider(options?: RecipeOptions & { color?: string }): UIFXRecipe;
|
|
46
|
+
export declare function CosmicSlider(options?: RecipeOptions & { dustCount?: number }): UIFXRecipe;
|
|
47
|
+
export declare function LaserSlider(options?: RecipeOptions): UIFXRecipe;
|
|
22
48
|
|
|
23
49
|
// -- Vol.2: Toggles --
|
|
24
|
-
export declare function PendulumToggle(): UIFXRecipe;
|
|
25
|
-
export declare function CircuitToggle(options?:
|
|
26
|
-
export declare function LightningToggle(options?:
|
|
27
|
-
export declare function DNAToggle(): UIFXRecipe;
|
|
50
|
+
export declare function PendulumToggle(options?: RecipeOptions): UIFXRecipe;
|
|
51
|
+
export declare function CircuitToggle(options?: RecipeOptions): UIFXRecipe;
|
|
52
|
+
export declare function LightningToggle(options?: RecipeOptions): UIFXRecipe;
|
|
53
|
+
export declare function DNAToggle(options?: RecipeOptions): UIFXRecipe;
|
|
28
54
|
|
|
29
55
|
// -- Vol.2: Buttons --
|
|
30
|
-
export declare function HeartbeatButton(options?:
|
|
31
|
-
export declare function BreathingButton(): UIFXRecipe;
|
|
32
|
-
export declare function InkSplashButton(options?:
|
|
33
|
-
export declare function PixelDissolveButton(options?:
|
|
34
|
-
export declare function FireworkButton(options?:
|
|
56
|
+
export declare function HeartbeatButton(options?: RecipeOptions): UIFXRecipe;
|
|
57
|
+
export declare function BreathingButton(options?: RecipeOptions): UIFXRecipe;
|
|
58
|
+
export declare function InkSplashButton(options?: RecipeOptions): UIFXRecipe;
|
|
59
|
+
export declare function PixelDissolveButton(options?: RecipeOptions & { cols?: number; rows?: number }): UIFXRecipe;
|
|
60
|
+
export declare function FireworkButton(options?: RecipeOptions): UIFXRecipe;
|
|
35
61
|
|
|
36
62
|
// -- Vol.2: Sliders --
|
|
37
|
-
export declare function AuroraSlider(): UIFXRecipe;
|
|
38
|
-
export declare function WaveSlider(options?:
|
|
39
|
-
export declare function ElasticBandSlider(): UIFXRecipe;
|
|
40
|
-
export declare function GravitySlider(): UIFXRecipe;
|
|
63
|
+
export declare function AuroraSlider(options?: RecipeOptions): UIFXRecipe;
|
|
64
|
+
export declare function WaveSlider(options?: RecipeOptions): UIFXRecipe;
|
|
65
|
+
export declare function ElasticBandSlider(options?: RecipeOptions): UIFXRecipe;
|
|
66
|
+
export declare function GravitySlider(options?: RecipeOptions): UIFXRecipe;
|
|
41
67
|
|
|
42
68
|
// -- Vol.2: Loaders --
|
|
43
|
-
export declare function OrbitLoader(): UIFXRecipe;
|
|
44
|
-
export declare function HelixLoader(): UIFXRecipe;
|
|
69
|
+
export declare function OrbitLoader(options?: RecipeOptions): UIFXRecipe;
|
|
70
|
+
export declare function HelixLoader(options?: RecipeOptions): UIFXRecipe;
|
|
45
71
|
|
|
46
72
|
// -- Vol.2: Checkboxes --
|
|
47
|
-
export declare function RippleCheck(): UIFXRecipe;
|
|
48
|
-
export declare function MorphCheck(): UIFXRecipe;
|
|
73
|
+
export declare function RippleCheck(options?: RecipeOptions): UIFXRecipe;
|
|
74
|
+
export declare function MorphCheck(options?: RecipeOptions): UIFXRecipe;
|
|
49
75
|
|
|
50
76
|
// -- Vol.2: Counters --
|
|
51
|
-
export declare function FlameCounter(options?:
|
|
52
|
-
export declare function GlitchCounter(options?:
|
|
77
|
+
export declare function FlameCounter(options?: RecipeOptions): UIFXRecipe;
|
|
78
|
+
export declare function GlitchCounter(options?: RecipeOptions): UIFXRecipe;
|
|
53
79
|
|
|
54
80
|
// -- Vol.2: Rating --
|
|
55
|
-
export declare function BubbleRating(options?:
|
|
81
|
+
export declare function BubbleRating(options?: RecipeOptions): UIFXRecipe;
|
|
56
82
|
|
|
57
83
|
// -- Vol.3: Knobs --
|
|
58
|
-
export declare function VolumeKnob(): UIFXRecipe;
|
|
59
|
-
export declare function CompassKnob(): UIFXRecipe;
|
|
84
|
+
export declare function VolumeKnob(options?: RecipeOptions): UIFXRecipe;
|
|
85
|
+
export declare function CompassKnob(options?: RecipeOptions): UIFXRecipe;
|
|
60
86
|
|
|
61
87
|
// -- Vol.3: Progress --
|
|
62
|
-
export declare function RingProgress(options?:
|
|
63
|
-
export declare function BatteryGauge(): UIFXRecipe;
|
|
64
|
-
export declare function SignalMeter(): UIFXRecipe;
|
|
88
|
+
export declare function RingProgress(options?: RecipeOptions): UIFXRecipe;
|
|
89
|
+
export declare function BatteryGauge(options?: RecipeOptions): UIFXRecipe;
|
|
90
|
+
export declare function SignalMeter(options?: RecipeOptions): UIFXRecipe;
|
|
65
91
|
|
|
66
92
|
// -- Vol.3: Controls --
|
|
67
|
-
export declare function PillTabs(): UIFXRecipe;
|
|
68
|
-
export declare function Stepper(): UIFXRecipe;
|
|
69
|
-
export declare function RadioOrbit(): UIFXRecipe;
|
|
93
|
+
export declare function PillTabs(options?: RecipeOptions): UIFXRecipe;
|
|
94
|
+
export declare function Stepper(options?: RecipeOptions): UIFXRecipe;
|
|
95
|
+
export declare function RadioOrbit(options?: RecipeOptions): UIFXRecipe;
|
|
70
96
|
|
|
71
97
|
// -- Vol.3: Indicators --
|
|
72
|
-
export declare function PasswordStrength(): UIFXRecipe;
|
|
73
|
-
export declare function WaterLevel(): UIFXRecipe;
|
|
74
|
-
export declare function HeatMap(options?:
|
|
98
|
+
export declare function PasswordStrength(options?: RecipeOptions): UIFXRecipe;
|
|
99
|
+
export declare function WaterLevel(options?: RecipeOptions): UIFXRecipe;
|
|
100
|
+
export declare function HeatMap(options?: RecipeOptions): UIFXRecipe;
|
|
75
101
|
|
|
76
102
|
// -- Vol.3: Mood --
|
|
77
|
-
export declare function DayNightToggle(options?:
|
|
78
|
-
export declare function ReactionPicker(): UIFXRecipe;
|
|
79
|
-
export declare function NotificationBell(): UIFXRecipe;
|
|
103
|
+
export declare function DayNightToggle(options?: RecipeOptions): UIFXRecipe;
|
|
104
|
+
export declare function ReactionPicker(options?: RecipeOptions): UIFXRecipe;
|
|
105
|
+
export declare function NotificationBell(options?: RecipeOptions): UIFXRecipe;
|
|
80
106
|
|
|
81
107
|
// -- Vol.3: Feedback --
|
|
82
|
-
export declare function TypewriterField(): UIFXRecipe;
|
|
83
|
-
export declare function SoundWaveBtn(): UIFXRecipe;
|
|
84
|
-
export declare function UploadProgress(): UIFXRecipe;
|
|
108
|
+
export declare function TypewriterField(options?: RecipeOptions): UIFXRecipe;
|
|
109
|
+
export declare function SoundWaveBtn(options?: RecipeOptions): UIFXRecipe;
|
|
110
|
+
export declare function UploadProgress(options?: RecipeOptions): UIFXRecipe;
|
|
85
111
|
|
|
86
112
|
// -- Vol.3: Fun --
|
|
87
|
-
export declare function ScratchReveal(options?:
|
|
88
|
-
export declare function TimerCountdown(): UIFXRecipe;
|
|
89
|
-
export declare function PullRefresh(): UIFXRecipe;
|
|
113
|
+
export declare function ScratchReveal(options?: RecipeOptions): UIFXRecipe;
|
|
114
|
+
export declare function TimerCountdown(options?: RecipeOptions): UIFXRecipe;
|
|
115
|
+
export declare function PullRefresh(options?: RecipeOptions): UIFXRecipe;
|
|
90
116
|
|
|
91
117
|
// ===========================================================
|
|
92
118
|
// BARREL OBJECTS (back-compat)
|