@neuralfog/elemix 0.2.1 → 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/README.md CHANGED
@@ -61,6 +61,18 @@ export class HelloWorld extends Component {
61
61
  }
62
62
  ```
63
63
 
64
+ ## Cloak
65
+
66
+ Every component is created with a `data-cloak` attribute that is removed automatically once the first render completes. Use it to prevent flash of unrendered content — hide components in global CSS until they're ready:
67
+
68
+ ```css
69
+ [data-cloak] {
70
+ visibility: hidden;
71
+ }
72
+ ```
73
+
74
+ Once the component mounts and finishes its first render, `data-cloak` is dropped and the element becomes visible.
75
+
64
76
  ## Rendering
65
77
 
66
78
  Renders are triggered implicitly by mutating `@state()` properties or subscribed signals. To trigger a render manually, call `this.render()`. This is useful when you want fine control over rendering without reactivity getting in the way.
@@ -81,6 +93,22 @@ export class UserCard extends Component {
81
93
  }
82
94
  ```
83
95
 
96
+ ## Self-Closing Tags
97
+
98
+ Custom elements and other non-void HTML elements can be written in self-closing form. The renderer expands `<my-tag />` to `<my-tag></my-tag>` before parsing, so you can drop the redundant closing tag whenever the element has no children.
99
+
100
+ ```typescript
101
+ template(): Template {
102
+ return html`
103
+ <pf-icon-search />
104
+ <pf-divider />
105
+ <pf-card data-variant="primary" />
106
+ `;
107
+ }
108
+ ```
109
+
110
+ Void HTML elements (`br`, `hr`, `img`, `input`, `link`, `meta`, etc.) are left alone — they're already self-closing per the HTML spec.
111
+
84
112
  ## Props
85
113
 
86
114
  Primitive props are shallow-diffed and only trigger updates when the value changes. Object props are passed through reactively. Functions are assigned once.
@@ -215,7 +243,11 @@ export class Form extends Component {
215
243
 
216
244
  ## Signals
217
245
 
218
- Signals are reactive stores shared across components. Define a signal as a standalone module and subscribe components via the `@component` decorator.
246
+ Signals are reactive stores shared across components. Read a signal's value inside a component's `template()` and the component re-renders whenever that signal changes — **no manual subscription needed**.
247
+
248
+ Subscription is fully automatic: while `template()` runs, every `signal.value.x` access tells the framework "this component depends on this signal." After the render, the component is subscribed to every signal it actually read. On the next render, stale subscriptions are dropped and current ones re-tracked, so conditional reads stay correct and nothing leaks. When the component leaves the DOM, all of its signal subscriptions are cleaned up automatically.
249
+
250
+ You don't register signals anywhere. You just read them.
219
251
 
220
252
  ### Definition
221
253
 
@@ -230,15 +262,13 @@ export const counter = signal({
230
262
 
231
263
  ### Usage
232
264
 
233
- Subscribe to a signal by passing it to the `signals` array. Access its state via `signal.value` and mutate it directly to trigger re-renders in all subscribed components.
234
-
235
265
  ```typescript
236
266
  import { Component, html, type Template } from '@neuralfog/elemix';
237
267
  import { component } from '@neuralfog/elemix/decorators';
238
268
 
239
269
  import { counter } from '../signals/counter';
240
270
 
241
- @component({ signals: [counter] })
271
+ @component()
242
272
  export class CounterDisplay extends Component {
243
273
  template(): Template {
244
274
  const { count, label } = counter.value;
@@ -0,0 +1 @@
1
+ const e=require("./renderers-BBOJdSjj.js");var t=class{renderTrigger;subscribers=new Set;proxy;proxySet=new WeakSet;get value(){return this.proxy}constructor(e,t){this.renderTrigger=t,this.proxy=this.create(e)}subscribe(e){return this.subscribers.has(e)||this.subscribers.add(e),this}unsubscribe(e){return this.subscribers.delete(e),this}create(t){let n=this;return new Proxy(t,{get(t,i){let a=t[i];r(a);let o=e.n.active;if(o&&(n.subscribers.add(o),o.tracked.add(n)),typeof a==`object`&&a&&!n.proxySet.has(a)){let e=new Proxy(a,this);return n.proxySet.add(e),e}return a},set(e,t,i){return e[t]=i,r(e),n.notify(),!0}})}notify(){for(let e of this.subscribers)e.render(this.renderTrigger)}},n=`Reactive state does not support collections: Map, WeakMap, Set, WeakSet`,r=e=>{if(e instanceof Map||e instanceof WeakMap||e instanceof Set||e instanceof WeakSet)throw Error(n)};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return t}});
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./utilities-BN8G-efH.js");var n=(e,t)=>{customElements.get(e)===void 0&&customElements.define(e,t)},r=e=>r=>{let i=t.t(r.name),a=class extends r{constructor(){if(super(),e?.signals?.length)for(let t of e.signals)t.subscribe(this)}};r.$signals=e?.signals||[],r.$styles=e?.styles||[],n(i,a)},i=t=>(n,r)=>{n.stateProperties||=new Map,n.stateProperties.has(r)||n.stateProperties.set(r,t||e.t.LOCAL_STATE)};exports.component=r,exports.state=i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./utilities-COO_YT_a.js");var n=(e,t)=>{customElements.get(e)===void 0&&customElements.define(e,t)},r=e=>r=>{let i=t.t(r.name);r.$styles=e?.styles||[],n(i,r)},i=t=>(n,r)=>{n.stateProperties||=new Map,n.stateProperties.has(r)||n.stateProperties.set(r,t||e.t.LOCAL_STATE)};exports.component=r,exports.state=i;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./renderers-DuXHBLlK.js"),n=require("./Reactive-BR9C38bG.js"),r=require("./App-tMVVu8H5.js");let i=require("@neuralfog/elemix-renderer");var a=class{component;locked=!1;scheduledRenderTriggers=new Set;constructor(e){this.component=e}schedule(e,n=!1){this.component.template()&&(e&&this.scheduledRenderTriggers.add(e),this.locked||(this.locked=!0,t.t.add(this),setTimeout(()=>{this.render(Array.from(this.scheduledRenderTriggers)),this.scheduledRenderTriggers.clear(),this.locked=!1,t.t.delete(this),n&&this.component.onMount()},0)))}render(e){(0,i.render)(this.component.template(),this.component.root),this.component.onRender(e)}},o=class{component;constructor(e){this.component=e}initialize(){let e=this.component.constructor.prototype.stateProperties;if(e)for(let[t,r]of e){let e=this.component;e[t]=new n.t(e[t],r).subscribe(this.component).value}}},s=class{component;data={};constructor(e){this.component=e}initialize(){this.data=new n.t(this.data,e.t.PROPS).subscribe(this.component).value}setReactive(e,t){this.data[e]=t}set(e,t){if(typeof t==`object`&&t){this.setReactive(e,t);return}let n=this.data[e];if(typeof t==`function`){this.data[e]||this.setReactive(e,t);return}n!==t&&this.setReactive(e,t)}},c=class{component;styles;constructor(e){this.component=e,this.styles=this.component.constructor.$styles}initialize(){if(this.component.shadowRoot&&this.styles.length){let e=new CSSStyleSheet;e.replaceSync(this.styles.join(` `));let t=r.t.config.baseStyles||[];this.component.shadowRoot.adoptedStyleSheets=[...t,e,...this.component.controlStyles]}}},l=class extends HTMLElement{$props=new s(this);$renderer=new a(this);$localState=new o(this);$styles=new c(this);$controlStyles;get root(){return this.shadowRoot}get props(){return this.$props.data}get styles(){return this.$styles}get controlStyles(){return this.$controlStyles||[]}constructor(){super(),this.attachShadow({mode:`open`})}connectedCallback(){this.beforeMount(),this.$styles.initialize(),this.$props.initialize(),this.$localState.initialize(),this.render(e.t.ON_MOUNT,!0)}disconnectedCallback(){t.t.delete(this.$renderer),this.unsubscribeFromSignals(),this.onDispose()}template(){}onRender(e){}beforeMount(){}onMount(){}onDispose(){}render(e,t=!1){this.$renderer.schedule(e,t)}setControlStyles(e){this.$controlStyles=e}unsubscribeFromSignals(){for(let e of this.constructor.$signals)e.unsubscribe(this)}hasSlot(e){return Array.from(this.children).some(t=>t.getAttribute(`slot`)===e)}};exports.Component=l,exports.RenderTrigger=e.t,exports.html=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./renderers-BBOJdSjj.js"),n=require("./Reactive-CQV_s8zX.js"),r=require("./App-tMVVu8H5.js");let i=require("@neuralfog/elemix-renderer");var a=class{component;locked=!1;scheduledRenderTriggers=new Set;constructor(e){this.component=e}schedule(e,n=!1){let r=t.n.active;t.n.active=null;let i;try{i=this.component.template()}finally{t.n.active=r}i&&(e&&this.scheduledRenderTriggers.add(e),this.locked||(this.locked=!0,t.t.add(this),setTimeout(()=>{this.render(Array.from(this.scheduledRenderTriggers)),this.scheduledRenderTriggers.clear(),this.locked=!1,t.t.delete(this),n&&(this.component.onMount(),this.component.removeAttribute(`data-cloak`))},0)))}render(e){for(let e of this.component.tracked)e.unsubscribe(this.component);this.component.tracked.clear();let n=t.n.active;t.n.active=this.component;try{(0,i.render)(this.component.template(),this.component.root)}finally{t.n.active=n}this.component.onRender(e)}},o=class{component;constructor(e){this.component=e}initialize(){let e=this.component.constructor.prototype.stateProperties;if(e)for(let[t,r]of e){let e=this.component;e[t]=new n.t(e[t],r).subscribe(this.component).value}}},s=class{component;data={};constructor(e){this.component=e}initialize(){this.data=new n.t(this.data,e.t.PROPS).subscribe(this.component).value}setReactive(e,t){this.data[e]=t}set(e,t){if(typeof t==`object`&&t){this.setReactive(e,t);return}let n=this.data[e];if(typeof t==`function`){this.data[e]||this.setReactive(e,t);return}n!==t&&this.setReactive(e,t)}},c=class{component;styles;constructor(e){this.component=e,this.styles=this.component.constructor.$styles}initialize(){if(this.component.shadowRoot&&this.styles.length){let e=new CSSStyleSheet;e.replaceSync(this.styles.join(` `));let t=r.t.config.baseStyles||[];this.component.shadowRoot.adoptedStyleSheets=[...t,e,...this.component.controlStyles]}}},l=class extends HTMLElement{$props=new s(this);$renderer=new a(this);$localState=new o(this);$styles=new c(this);$controlStyles;tracked=new Set;get root(){return this.shadowRoot}get props(){return this.$props.data}get styles(){return this.$styles}get controlStyles(){return this.$controlStyles||[]}constructor(){super(),this.attachShadow({mode:`open`}),this.setAttribute(`data-cloak`,``)}connectedCallback(){this.beforeMount(),this.$styles.initialize(),this.$props.initialize(),this.$localState.initialize(),this.render(e.t.ON_MOUNT,!0)}disconnectedCallback(){t.t.delete(this.$renderer),this.unsubscribeFromSignals(),this.onDispose()}template(){}onRender(e){}beforeMount(){}onMount(){}onDispose(){}render(e,t=!1){this.$renderer.schedule(e,t)}setControlStyles(e){this.$controlStyles=e}unsubscribeFromSignals(){for(let e of this.tracked)e.unsubscribe(this);this.tracked.clear()}hasSlot(e){return Array.from(this.children).some(t=>t.getAttribute(`slot`)===e)}};exports.Component=l,exports.RenderTrigger=e.t,exports.html=e.n;
package/dist/reactive.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Reactive-BR9C38bG.js");exports.Reactive=e.t,exports.UNSUPPORTED_COLLECTION_ERROR_MESSAGE=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Reactive-CQV_s8zX.js");exports.Reactive=e.t,exports.UNSUPPORTED_COLLECTION_ERROR_MESSAGE=e.n;
@@ -0,0 +1 @@
1
+ var e=new Set,t={active:null};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
package/dist/signal.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./Reactive-BR9C38bG.js");var n=(n,r)=>new t.t(n,r||e.t.SIGNAL);exports.signal=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types-v1ChTfwQ.js"),t=require("./Reactive-CQV_s8zX.js");var n=(n,r)=>new t.t(n,r||e.t.SIGNAL);exports.signal=n;
@@ -1,11 +1,20 @@
1
1
  import { type RenderTriggerType, type Template } from '../types';
2
2
  import { Styles } from './Styles';
3
+ type Trackable = {
4
+ unsubscribe(c: Component): unknown;
5
+ };
3
6
  export declare class Component<ComponentProps = unknown> extends HTMLElement {
4
7
  private $props;
5
8
  private $renderer;
6
9
  private $localState;
7
10
  private $styles;
8
11
  private $controlStyles?;
12
+ /**
13
+ * Signals auto-subscribed during template() execution via the
14
+ * renderTracking mechanism. Cleared and rebuilt on every render so
15
+ * conditional reads correctly add and remove subscriptions.
16
+ */
17
+ tracked: Set<Trackable>;
9
18
  get root(): HTMLElement | ShadowRoot | null;
10
19
  get props(): ComponentProps;
11
20
  get styles(): Styles;
@@ -23,3 +32,4 @@ export declare class Component<ComponentProps = unknown> extends HTMLElement {
23
32
  private unsubscribeFromSignals;
24
33
  hasSlot(name: string): boolean;
25
34
  }
35
+ export {};
@@ -1,6 +1,4 @@
1
- import type { Reactive } from '../Reactive';
2
1
  type ComponentDecoratorConfig = {
3
- signals?: Reactive<unknown>[];
4
2
  styles?: string[];
5
3
  };
6
4
  type Component = any;
@@ -1,2 +1,13 @@
1
1
  import type { Renderer } from './component/Renderer';
2
+ import type { Component } from './component/Component';
2
3
  export declare const activeRenderers: Set<Renderer>;
4
+ /**
5
+ * Tracks which component is currently executing template() so that
6
+ * Reactive proxies can auto-subscribe the component when its template
7
+ * reads a signal value.
8
+ *
9
+ * Held inside an object so cross-module mutation is observable.
10
+ */
11
+ export declare const renderTracking: {
12
+ active: Component | null;
13
+ };
@@ -1 +1 @@
1
- const e=require("./renderers-DuXHBLlK.js");var t=(e=void 0)=>({value:e}),n=()=>Math.floor(performance.now()*1e3).toString(36)+Math.random().toString(36).slice(2,6);function r(){return new Promise(t=>{let n=()=>{e.t.size===0?t(!1):setTimeout(n,0)};n()})}var i=e=>(e.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|[0-9]*$)|[A-Z]?[a-z]+|[A-Z]|[0-9]+/g)||[]).map(e=>e.toLowerCase()).join(`-`),a=e=>{let t=new CSSStyleSheet;return t.replaceSync(e),t};Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return i}});
1
+ const e=require("./renderers-BBOJdSjj.js");var t=(e=void 0)=>({value:e}),n=()=>Math.floor(performance.now()*1e3).toString(36)+Math.random().toString(36).slice(2,6);function r(){return new Promise(t=>{let n=()=>{e.t.size===0?t(!1):setTimeout(n,0)};n()})}var i=e=>(e.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|[0-9]*$)|[A-Z]?[a-z]+|[A-Z]|[0-9]+/g)||[]).map(e=>e.toLowerCase()).join(`-`),a=e=>{let t=new CSSStyleSheet;return t.replaceSync(e),t};Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return i}});
package/dist/utilities.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunk-NGjzN6Tu.js"),t=require("./utilities-BN8G-efH.js");var n=e.t((e=>{var t={EVENT:0,PROP:1,MODEL:2,STD:3,REF:4,BIND_ATTRS:5,BIND_EVENTS:6,DIRECT_CLASS:7},n=RegExp(`₥(\\d+)`),r=e=>{let t=e.match(n);if(!t)throw Error(`Unable to extract index from hole comment`);return Number(t[1])},i=e=>`<!--₥${e}-->`,a=e=>e.replace(/(\S+)=((<!--[\s\S]*?-->)|([^\s">]+))/g,`$1="$2"`),o=e=>e.replace(/([A-Z])/g,e=>`-${e.toLowerCase()}`),s=(e,t)=>{let n=new Set,r=``,i=e=>{let t=e.split(` `);for(let e=0;e<t.length;e++){let i=t[e];i&&!n.has(i)&&(n.add(i),r.length&&(r+=` `),r+=i)}};return i(e),i(t),r};Object.defineProperty(e,"a",{enumerable:!0,get:function(){return s}}),Object.defineProperty(e,"i",{enumerable:!0,get:function(){return i}}),Object.defineProperty(e,"n",{enumerable:!0,get:function(){return a}}),Object.defineProperty(e,"o",{enumerable:!0,get:function(){return t}}),Object.defineProperty(e,"r",{enumerable:!0,get:function(){return r}}),Object.defineProperty(e,"t",{enumerable:!0,get:function(){return o}})})),r=e.t((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`}),e.mergeClasses=n().a}))();exports.camelToKebabCase=t.t,exports.fastUID=t.n,exports.makeCssStylesheet=t.r,Object.defineProperty(exports,"mergeClasses",{enumerable:!0,get:function(){return r.mergeClasses}}),exports.ref=t.i,exports.render=t.a;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunk-NGjzN6Tu.js"),t=require("./utilities-COO_YT_a.js");var n=e.t((e=>{var t={EVENT:0,PROP:1,MODEL:2,STD:3,REF:4,BIND_ATTRS:5,BIND_EVENTS:6,DIRECT_CLASS:7},n=RegExp(`₥(\\d+)`),r=e=>{let t=e.match(n);if(!t)throw Error(`Unable to extract index from hole comment`);return Number(t[1])},i=e=>`<!--₥${e}-->`,a=e=>e.replace(/(\S+)=((<!--[\s\S]*?-->)|([^\s">]+))/g,`$1="$2"`),o=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`source`,`track`,`wbr`]),s=/<([a-zA-Z][a-zA-Z0-9-]*)([^>]*?)\s*\/>/g,c=e=>e.replace(s,(e,t,n)=>o.has(t.toLowerCase())?e:`<${t}${n}></${t}>`),l=e=>e.replace(/([A-Z])/g,e=>`-${e.toLowerCase()}`),u=(e,t)=>{let n=new Set,r=``,i=e=>{let t=e.split(` `);for(let e=0;e<t.length;e++){let i=t[e];i&&!n.has(i)&&(n.add(i),r.length&&(r+=` `),r+=i)}};return i(e),i(t),r};Object.defineProperty(e,"a",{enumerable:!0,get:function(){return i}}),Object.defineProperty(e,"i",{enumerable:!0,get:function(){return r}}),Object.defineProperty(e,"n",{enumerable:!0,get:function(){return a}}),Object.defineProperty(e,"o",{enumerable:!0,get:function(){return u}}),Object.defineProperty(e,"r",{enumerable:!0,get:function(){return c}}),Object.defineProperty(e,"s",{enumerable:!0,get:function(){return t}}),Object.defineProperty(e,"t",{enumerable:!0,get:function(){return l}})})),r=e.t((e=>{Object.defineProperty(e,Symbol.toStringTag,{value:`Module`}),e.mergeClasses=n().o}))();exports.camelToKebabCase=t.t,exports.fastUID=t.n,exports.makeCssStylesheet=t.r,Object.defineProperty(exports,"mergeClasses",{enumerable:!0,get:function(){return r.mergeClasses}}),exports.ref=t.i,exports.render=t.a;
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@neuralfog/elemix",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "author": "brownhounds",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
- "files": [
9
- "./dist/**/*"
10
- ],
8
+ "files": ["./dist/**/*"],
11
9
  "exports": {
12
10
  ".": "./dist/index.js",
13
11
  "./decorators": "./dist/decorators.js",
@@ -45,6 +43,6 @@
45
43
  "vitest": "4.1.7"
46
44
  },
47
45
  "peerDependencies": {
48
- "@neuralfog/elemix-renderer": "0.3.0"
46
+ "@neuralfog/elemix-renderer": "0.4.0"
49
47
  }
50
48
  }
@@ -1 +0,0 @@
1
- var e=class{renderTrigger;subscribers=new Set;proxy;proxySet=new WeakSet;get value(){return this.proxy}constructor(e,t){this.renderTrigger=t,this.proxy=this.create(e)}subscribe(e){return this.subscribers.has(e)||this.subscribers.add(e),this}unsubscribe(e){return this.subscribers.delete(e),this}create(e){let t=this;return new Proxy(e,{get(e,r){let i=e[r];if(n(i),typeof i==`object`&&i&&!t.proxySet.has(i)){let e=new Proxy(i,this);return t.proxySet.add(e),e}return i},set(e,r,i){return e[r]=i,n(e),t.notify(),!0}})}notify(){for(let e of this.subscribers)e.render(this.renderTrigger)}},t=`Reactive state does not support collections: Map, WeakMap, Set, WeakSet`,n=e=>{if(e instanceof Map||e instanceof WeakMap||e instanceof Set||e instanceof WeakSet)throw Error(t)};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
@@ -1 +0,0 @@
1
- var e=new Set;Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});