aberdeen 1.4.1 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aberdeen",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "author": "Frank van Viegen",
5
5
  "main": "dist-min/aberdeen.js",
6
6
  "devDependencies": {
package/skill/SKILL.md CHANGED
@@ -30,7 +30,7 @@ $('input placeholder="Something containing spaces" value=', userInput);
30
30
  $('button text=', `Count: ${state.count}`);
31
31
 
32
32
  // Event handlers
33
- $('button#Click click=', () => console.log('clicked'));
33
+ $('button text=Click click=', () => console.log('clicked'));
34
34
 
35
35
  // Nested content via function (creates reactive scope)
36
36
  $('ul', () => {
@@ -78,18 +78,23 @@ $('button', { click: handler, '.active': isActive });
78
78
  | `r` | borderRadius | | |
79
79
 
80
80
  ### CSS Variables (`@`)
81
- Values starting with `@` reference `cssVars`. Predefined spacing scale:
82
- | Var | Value | Var | Value |
83
- |-----|-------|-----|-------|
84
- | `@1` | 0.25rem | `@4` | 2rem |
85
- | `@2` | 0.5rem | `@5` | 4rem |
86
- | `@3` | 1rem | `@n` | 2^(n-3) rem |
81
+ Values starting with `@` expand to native CSS custom properties via `var(--name)`. Numeric keys are prefixed with `m` (e.g., `@3` → `var(--m3)`).
82
+
83
+ Predefined spacing scale:
84
+ | Var | CSS Output | Value |
85
+ |-----|------------|-------|
86
+ | `@1` | `var(--m1)` | 0.25rem |
87
+ | `@2` | `var(--m2)` | 0.5rem |
88
+ | `@3` | `var(--m3)` | 1rem |
89
+ | `@4` | `var(--m4)` | 2rem |
90
+ | `@5` | `var(--m5)` | 4rem |
91
+ | `@n` | `var(--mn)` | 2^(n-3) rem |
87
92
 
88
93
  **Best practice:** Use `@3` and `@4` for most margins/paddings. For new projects, define color variables:
89
94
  ```typescript
90
95
  cssVars.primary = '#3b82f6';
91
96
  cssVars.danger = '#ef4444';
92
- $('button bg:@primary fg:white#Save');
97
+ $('button bg:@primary fg:white#Save'); // outputs: background: var(--primary); color: white
93
98
  ```
94
99
 
95
100
  ## Reactive State: `proxy()`
package/src/aberdeen.ts CHANGED
@@ -53,7 +53,7 @@ function queue(runner: QueueRunner) {
53
53
  * ```typescript
54
54
  * const data = proxy("before");
55
55
  *
56
- * $({text: data});
56
+ * $('#'+data);
57
57
  * console.log(1, document.body.innerHTML); // before
58
58
  *
59
59
  * // Make an update that should cause the DOM to change.
@@ -940,9 +940,9 @@ const EMPTY = Symbol("empty");
940
940
  * // Reactively display a message if the items array is empty
941
941
  * $('div', () => {
942
942
  * if (isEmpty(items)) {
943
- * $('p', 'i#No items yet!');
943
+ * $('p i#No items yet!');
944
944
  * } else {
945
- * onEach(items, item=>$('p#'+item));
945
+ * onEach(items, item => $('p#'+item));
946
946
  * }
947
947
  * });
948
948
  *
@@ -995,7 +995,7 @@ export interface ValueRef<T> {
995
995
  * const cnt = count(items);
996
996
  *
997
997
  * // Create a DOM text node for the count:
998
- * $('div', {text: cnt});
998
+ * $('div text=', cnt);
999
999
  * // <div>2</div>
1000
1000
 
1001
1001
  * // Or we can use it in an {@link derive} function:
@@ -1704,17 +1704,20 @@ export const NO_COPY = Symbol("NO_COPY");
1704
1704
  (Promise.prototype as any)[NO_COPY] = true;
1705
1705
 
1706
1706
  /**
1707
- * CSS variable lookup table for the `@` value prefix.
1707
+ * CSS variables that are output as native CSS custom properties.
1708
1708
  *
1709
- * When a CSS value starts with `@`, the rest is used as a key to look up the actual value.
1709
+ * When a CSS value starts with `@`, it becomes `var(--name)` (or `var(--mN)` for numeric keys).
1710
1710
  * Pre-initialized with keys '1'-'12' mapping to an exponential rem scale (e.g., @1=0.25rem, @3=1rem).
1711
1711
  *
1712
+ * Changes to cssVars are automatically reflected in a `<style>` tag in `<head>`, making updates
1713
+ * reactive across all elements using those variables.
1714
+ *
1712
1715
  * @example
1713
1716
  * ```typescript
1714
1717
  * cssVars.primary = '#3b82f6';
1715
1718
  * cssVars[3] = '16px'; // Override @3 to be 16px instead of 1rem
1716
- * $('p color:@primary'); // Sets color to #3b82f6
1717
- * $('div mt:@3'); // Sets margin-top to 16px
1719
+ * $('p color:@primary'); // Sets color to var(--primary)
1720
+ * $('div mt:@3'); // Sets margin-top to var(--m3)
1718
1721
  * ```
1719
1722
  */
1720
1723
  export const cssVars: Record<string, string> = optProxy({});
@@ -1723,6 +1726,13 @@ for (let i = 1; i <= 12; i++) {
1723
1726
  cssVars[i] = 2 ** (i - 3) + "rem";
1724
1727
  }
1725
1728
 
1729
+ const DIGIT_FIRST = /^\d/;
1730
+ function cssVarRef(name: string): string {
1731
+ // Prefix numeric keys with 'm' (CSS custom property names can't start with a digit)
1732
+ const varName = DIGIT_FIRST.test(name) ? `m${name}` : name;
1733
+ return `var(--${varName})`;
1734
+ }
1735
+
1726
1736
  /**
1727
1737
  * Clone an (optionally proxied) object or array.
1728
1738
  *
@@ -1782,17 +1792,10 @@ const refHandler: ProxyHandler<RefTarget> = {
1782
1792
  * const formData = proxy({ color: 'orange', velocity: 42 });
1783
1793
  *
1784
1794
  * // Usage with `bind`
1785
- * $('input', {
1786
- * type: 'text',
1787
- * // Creates a two-way binding between the input's value and formData.username
1788
- * bind: ref(formData, 'color')
1789
- * });
1795
+ * $('input type=text bind=', ref(formData, 'color'));
1790
1796
  *
1791
1797
  * // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
1792
- * $('p#Selected color: ', {
1793
- * text: ref(formData, 'color'),
1794
- * $color: ref(formData, 'color')
1795
- * });
1798
+ * $('p text="Selected color: " text=', ref(formData, 'color'), 'color:', ref(formData, 'color'));
1796
1799
  *
1797
1800
  * // Changes are actually stored in formData - this causes logs like `{color: "Blue", velocity 42}`
1798
1801
  * $(() => console.log(formData))
@@ -1926,16 +1929,7 @@ const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
1926
1929
  *
1927
1930
  * @example Create Element
1928
1931
  * ```typescript
1929
- * $('button.secondary.outline#Submit', {
1930
- * disabled: false,
1931
- * click: () => console.log('Clicked!'),
1932
- * $color: 'red'
1933
- * });
1934
- * ```
1935
- *
1936
- * Which can also be written as:
1937
- * ```typescript
1938
- * $('button.secondary.outline text=Submit $color=red disabled=', false, 'click=', () => console.log('Clicked!'));
1932
+ * $('button.secondary.outline text=Submit color:red disabled=', false, 'click=', () => console.log('Clicked!'));
1939
1933
  * ```
1940
1934
  *
1941
1935
  * We want to set `disabled` as a property instead of an attribute, so we must use the `key=` syntax in order to provide
@@ -1943,7 +1937,7 @@ const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
1943
1937
  *
1944
1938
  * @example Create Nested Elements
1945
1939
  * ```typescript
1946
- * let inputElement: Element = $('label#Click me', 'input', {type: 'checkbox'});
1940
+ * let inputElement: Element = $('label text="Click me" input type=checkbox');
1947
1941
  * // You should usually not touch raw DOM elements, unless when integrating
1948
1942
  * // with non-Aberdeen code.
1949
1943
  * console.log('DOM element:', inputElement);
@@ -1955,14 +1949,14 @@ const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
1955
1949
  * $('div', () => { // Outer element
1956
1950
  * // This scope re-renders when state.count changes
1957
1951
  * $(`p#Count is ${state.count}`);
1958
- * $('button#Increment', { click: () => state.count++ });
1952
+ * $('button text=Increment click=', () => state.count++);
1959
1953
  * });
1960
1954
  * ```
1961
1955
  *
1962
1956
  * @example Two-way Binding
1963
1957
  * ```typescript
1964
1958
  * const user = proxy({ name: '' });
1965
- * $('input', { placeholder: 'Name', bind: ref(user, 'name') });
1959
+ * $('input placeholder=Name bind=', ref(user, 'name'));
1966
1960
  * $('h3', () => { // Reactive scope
1967
1961
  * $(`#Hello ${user.name || 'stranger'}`);
1968
1962
  * });
@@ -1971,7 +1965,7 @@ const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
1971
1965
  * @example Conditional Rendering
1972
1966
  * ```typescript
1973
1967
  * const show = proxy(false);
1974
- * $('button', { click: () => show.value = !show.value }, () => $(show.value ? '#Hide' : '#Show'));
1968
+ * $('button click=', () => show.value = !show.value, () => $(show.value ? '#Hide' : '#Show'));
1975
1969
  * $(() => { // Reactive scope
1976
1970
  * if (show.value) {
1977
1971
  * $('p#Details are visible!');
@@ -2095,7 +2089,7 @@ let cssCount = 0;
2095
2089
  * - In case a selector contains a `&`, that character will be replaced by the parent selector.
2096
2090
  * - Selectors will be split on `,` characters, each combining with the parent selector with *or* semantics.
2097
2091
  * - Selector starting with `'@'` define at-rules like media queries. They may be nested within regular selectors.
2098
- * @param global - @deprecated Use {@link insertGlobalCss} instead.
2092
+ * @param global - Deprecated! Use {@link insertGlobalCss} instead.
2099
2093
  * @returns The unique class name prefix used for scoping (e.g., `.AbdStl1`). Use this
2100
2094
  * prefix with {@link $} to apply the styles.
2101
2095
  *
@@ -2194,7 +2188,7 @@ function styleToCss(style: object, prefix: string): string {
2194
2188
  );
2195
2189
  }
2196
2190
  } else {
2197
- const val = v == null || v === false ? "" : typeof v === 'string' ? (v[0] === '@' ? (cssVars as any)[v.substring(1)] || "" : v) : String(v);
2191
+ const val = v == null || v === false ? "" : typeof v === 'string' ? (v[0] === '@' ? cssVarRef(v.substring(1)) : v) : String(v);
2198
2192
  const expanded = CSS_SHORT[k] || k;
2199
2193
  for (const prop of (Array.isArray(expanded) ? expanded : [expanded])) {
2200
2194
  props += `${prop.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}:${val};`;
@@ -2223,7 +2217,7 @@ function applyArg(el: Element, key: string, value: any) {
2223
2217
  } else if (key[0] === "$") {
2224
2218
  // Style (with shortcuts)
2225
2219
  key = key.substring(1);
2226
- const val = value == null || value === false ? "" : typeof value === 'string' ? (value[0] === '@' ? (cssVars as any)[value.substring(1)] || "" : value) : String(value);
2220
+ const val = value == null || value === false ? "" : typeof value === 'string' ? (value[0] === '@' ? cssVarRef(value.substring(1)) : value) : String(value);
2227
2221
  const expanded = CSS_SHORT[key] || key;
2228
2222
  if (typeof expanded === "string") {
2229
2223
  (el as any).style[expanded] = val;
@@ -2374,7 +2368,7 @@ export function getParentElement(): Element {
2374
2368
  * })
2375
2369
  *
2376
2370
  * // Show the sum
2377
- * $('h1', {text: sum});
2371
+ * $('h1 text=', sum);
2378
2372
  *
2379
2373
  * // Make random changes to the array
2380
2374
  * const rnd = () => 0|(Math.random()*20);
@@ -2412,8 +2406,8 @@ export function clean(cleaner: () => void) {
2412
2406
  * // When data.notifications changes, only this inner scope reruns,
2413
2407
  * // leaving the `<p>Welcome, ..</p>` untouched.
2414
2408
  * console.log('Notifications');
2415
- * $('code.notification-badge#' + data.notifications);
2416
- * $('a#Notify!', {click: () => data.notifications++});
2409
+ * $('code.notification-badge text=', data.notifications);
2410
+ * $('a text=Notify! click=', () => data.notifications++);
2417
2411
  * });
2418
2412
  * });
2419
2413
  * ```
@@ -2522,7 +2516,7 @@ export function unmountAll() {
2522
2516
  *
2523
2517
  */
2524
2518
 
2525
- export function peek<T extends object>(target: T, key: keyof T): T[typeof key];
2519
+ export function peek<T extends object, K extends keyof T>(target: T, key: K): T[K];
2526
2520
  export function peek<K,V>(target: Map<K,V>, key: K): V | undefined;
2527
2521
  export function peek<T>(target: T[], key: number): T | undefined;
2528
2522
  export function peek<T>(target: () => T): T;
@@ -2919,3 +2913,21 @@ export function withEmitHandler(
2919
2913
  emit = oldEmitHandler;
2920
2914
  }
2921
2915
  }
2916
+
2917
+ // Initialize the cssVars style tag in document.head
2918
+ // This runs at module load time, after all functions are defined
2919
+ if (typeof document !== "undefined") {
2920
+ leakScope(() => {
2921
+ mount(document.head, () => {
2922
+ $('style', () => {
2923
+ let css = ":root {\n";
2924
+ for(const [key, value] of Object.entries(cssVars)) {
2925
+ const varName = DIGIT_FIRST.test(String(key)) ? `m${key}` : key;
2926
+ css += ` --${varName}: ${value};\n`;
2927
+ }
2928
+ css += "}";
2929
+ $(`#${css}`);
2930
+ })
2931
+ });
2932
+ });
2933
+ }