redgin 0.1.18 → 0.2.1

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
@@ -1,153 +1,226 @@
1
+ [![NPM](https://nodei.co/npm/redgin.svg?style=flat&data=v,d&color=red)](https://nodei.co/npm/redgin/)
2
+
1
3
  # RedGin
2
- # ~5.3kb Simplified library for building [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components), works on Vanilla JS / all JS framework
3
4
 
4
- * Use Javascript [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for Template syntax
5
- * Rerender element with [`watch`](https://stackblitz.com/edit/typescript-t3fqo8?file=sampleWatch.ts)
6
- * Create getter/setters with [`getset`](https://stackblitz.com/edit/typescript-t3fqo8?file=sampleWatch.ts)
7
- * Create Property reflection with [`propReflect`](https://stackblitz.com/edit/typescript-hlms7u?file=index.html)
8
- * Create Inline Events with [`event`](https://stackblitz.com/edit/typescript-t3fqo8?file=sampleWatch.ts)
9
- * Create custom events with [`emit`](https://stackblitz.com/edit/redgin-childtoparent?file=index.ts)
10
- * Inject Global Styles with [`injectStyles`](https://stackblitz.com/edit/redgin-bootstrap?file=index.ts)
11
- * [Support Typescript](https://stackblitz.com/edit/typescript-ue61k6?file=index.ts)
5
+ A lightweight (~5.3kb) library that solves the pain points of native Web Components. RedGin offers fine-grained reactivity, surgical updates, and intuitive APIs - making Web Components actually enjoyable to build.
12
6
 
7
+ ## Why RedGin?
13
8
 
14
- ## Install
9
+ Native Web Components are powerful but come with friction:
15
10
 
16
- ### Plug & Play, Import directly from cdn
11
+ | Pain Point | Native Web Components | RedGin |
12
+ |------------|----------------------|--------|
13
+ | **Boilerplate** | Manual lifecycle callbacks, attributeChangedCallback, getters/setters | Zero boilerplate with `getset` and `propReflect` |
14
+ | **Reactivity** | Manual observation with `attributeChangedCallback` | Automatic reactivity with `watch`, `s()`, and fine-grained updates |
15
+ | **Template Updates** | Manual DOM manipulation | Surgical updates - only changed parts re-render |
16
+ | **Attribute Reflection** | Manual sync between properties and attributes | Automatic with `propReflect` |
17
+ | **Event Binding** | `addEventListener` boilerplate | Inline events with `on()` |
18
+ | **Style Sharing** | Duplicated styles per component | Global `shareStyle` injection |
19
+ | **Performance** | Full re-renders on any change | Only changed elements update |
20
+ | **TypeScript** | Complex typing for custom elements | First-class TypeScript support |
17
21
 
18
- ```js
19
- // latest
20
- import { Redgin } from 'https://cdn.jsdelivr.net/npm/redgin@latest/dist/redgin.min.js'
22
+ ## Core Philosophy
21
23
 
22
- // or specific version
23
- import { RedGin } from 'https://cdn.jsdelivr.net/npm/redgin@0.1.18/dist/redgin.min.js'
24
+ RedGin is built around **surgical updates** - only the elements that need to change, change. No virtual DOM, no heavy diffing, just precise, targeted updates to your components.
24
25
 
25
- ```
26
+ ## Key Features
26
27
 
27
- ### Or Install using NPM
28
+ - **🎯 Surgical Rendering**: Update only what changes - perfect for large lists
29
+ - **📝 Template Literals**: Write components using familiar JS template syntax
30
+ - **⚡️ Fine-grained Reactivity**: Multiple reactivity patterns (`watch`, `s()`, `getset`, `propReflect`)
31
+ - **🔗 Attribute Binding**: Smart `attr()` helper for dynamic attributes
32
+ - **🔄 Property Reflection**: Sync properties with attributes using `propReflect`
33
+ - **📊 Reactive Getters/Setters**: Create reactive state with `getset`
34
+ - **🎨 Style Management**: Global style injection with `shareStyle` and scoped styles with `css`
35
+ - **📘 TypeScript Ready**: Full type safety and IntelliSense
28
36
 
29
- ```js
30
- npm i redgin
37
+ ## Installation
38
+
39
+ ### Via npm
40
+ ```bash
41
+ npm i redgin
31
42
  ```
32
43
 
33
- #### then import the library, helpers
44
+ ## Via CDN
34
45
 
35
46
  ```js
36
- import { Redgin, propReflect, getset, watch, event, emit, html, css } from 'redgin'
47
+ <script type="module" src="https://cdn.jsdelivr.net/npm/redgin@latest/dist/redgin.min.js"></script>
37
48
  ```
38
49
 
39
50
 
40
- ## How to use?
41
- ### Inline Events
42
- it uses <code>event</code> directive to create event listener behind the scene and automatically clear once the component is remove from DOM
51
+ ## Quick Start
52
+
43
53
  ```js
44
- class Event extends RedGin {
54
+ import { RedGin, getset, on, html } from 'redgin';
55
+
56
+ class Counter extends RedGin {
57
+ count = getset(0);
58
+
45
59
  render() {
46
- return html`<button
47
- ${ event('click', () => alert('Click Me') )}
48
- >Submit</button>`
49
- }
60
+ return html`
61
+ <button ${on('click', () => this.count++)}>
62
+ Count: ${this.count}
63
+ </button>
64
+ `;
65
+ }
50
66
  }
51
- customElements.define('sample-event', Event);
52
- ```
53
67
 
54
- ### List Render (Reactive)
55
- * its uses <code>propReflect</code> to dynamically create reactive props reflection define in observedAttributes()
56
- * its uses <code>watch</code> directives to rerender inside html when value change
57
- ```js
58
- class Loop extends RedGin {
59
- arr = propReflect([1, 2, 3])
60
- static observedAttributes = ['arr']
61
-
62
- render() {
63
- return html`<ul> ${ watch(['arr'], () =>
64
- this.arr.map( e => `Number: ${e}`)
65
- ).join('')
66
- }
67
- </ul>`
68
- }
69
- }
70
- customElements.define('sample-loop', Loop);
68
+ customElements.define('my-counter', Counter);
71
69
  ```
72
70
 
73
- ### IF condition (Reactive)
74
- * its uses <code>propReflect</code> to dynamically create reactive props reflection define in observedAttributes()
75
- * its uses <code>watch</code> directives to rerender inside html when value change
71
+ ## API Reference
72
+
73
+
74
+ ### Core Helpers
75
+
76
+ | Helper | Purpose | Example |
77
+ | :--- | :--- | :--- |
78
+ | `getset(initial)` | Creates reactive property with getter/setter | `count = getset(0)` |
79
+ | `propReflect(initial)` | Reactive property that reflects to attribute | `theme = propReflect('light')` |
80
+ | `watch(deps, callback)` | Fine-grained control - rerenders when specified dependencies change | `${watch(['count', 'theme'], () => html`<div>...</div>`)}` |
81
+ | `s(callback)` | Shorthand for reactive value binding | `${s(() => this.count)}` |
82
+ | `attr(name, callback)` | Surgical attribute binding | `${attr('disabled', () => !this.editable)}` |
83
+ | `on(event, handler)` | Event listener binding | `${on('click', () => this.save())}` |
84
+ | `html` | Template literal tag for HTML | `html`<div>Hello</div>`` |
85
+
86
+ ### Style Helpers
87
+
88
+ | Helper | Purpose | Example |
89
+ | :--- | :--- | :--- |
90
+ | `css` | Template literal tag for component-scoped styles | `styles = [css`.card { padding: 1rem; }`]` |
91
+ | `shareStyle(styles)` | Injects global styles across all components | `shareStyle(css:host { --brand: blue; })` |
92
+
93
+
94
+ ## Lifecycle Methods
95
+
96
+ * onInit() - After first render
97
+ * onDoUpdate() - After data sync
98
+ * onUpdated() - After every attribute change/requestUpdate
99
+
100
+ ## Style Management Examples
101
+
102
+ ### Global Design System with shareStyle
103
+ import { RedGin, shareStyle, css, html } from 'redgin';
104
+
105
+ // Share Bootstrap globally (injected once, used everywhere)
106
+ shareStyle('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">')
107
+
108
+ // Share design tokens across all components
76
109
  ```js
77
- class If extends RedGin {
78
- isDisable = propReflect(false)
79
- static observedAttributes = ['is-disable']
110
+ shareStyle(css`
111
+ :host {
112
+ --brand-primary: #007bff;
113
+ --brand-success: #28a745;
114
+ --card-shadow: 0 4px 6px rgba(0,0,0,0.1);
115
+ }
116
+
117
+ .rg-card {
118
+ border-radius: 8px;
119
+ box-shadow: var(--card-shadow);
120
+ transition: transform 0.2s;
121
+ }
122
+ `);
123
+
124
+ class ProductCard extends RedGin {
125
+ // Component-specific styles (merged with shared styles)
126
+ styles = [css`
127
+ .local-price { color: var(--brand-success); font-weight: bold; }
128
+ `]
80
129
 
81
130
  render() {
82
- return `
83
- ${ watch(['isDisable'], () => html`
84
- <button
85
- ${ this.isDisable ? `disable` : ``}
86
- > Submit</button>`
87
- )
88
- }
89
- `
131
+ // Bootstrap classes work without local import!
132
+ return html`
133
+ <div class="rg-card p-3">
134
+ <div class="local-price">$120.00</div>
135
+ <button class="btn btn-primary">Buy Now</button>
136
+ </div>
137
+ `;
90
138
  }
91
139
  }
92
- customElements.define('sample-if', If);
93
140
  ```
94
141
 
95
- ### Render Obj (Reactive)
96
- * recommend to use watch directive when rendering obj
97
- ```js
98
- obj = getset({
99
- id:1,
100
- name:'John Doe'
101
- }) //for complex just define a setter/getter manually?
142
+ ## Reactivity Patterns: watch vs s()
102
143
 
103
-
104
- render() {
105
- return `${ watch(['obj'], () =>
106
- html`<div>${ this.obj.id }</div>
107
- <div>${ this.obj.name }</div>`
108
- ) }`
144
+ ### RedGin offers two complementary reactivity patterns:
145
+
146
+ ## s() - Smart Auto-detection
147
+ ```js
148
+ // Automatically tracks dependencies
149
+ render() {
150
+ return html`
151
+ <div>${s(() => this.count)}</div>
152
+ <div>${s(() => this.theme)}</div>
153
+ `;
109
154
  }
110
155
  ```
111
156
 
112
- ### Render List of Obj (Reactive)
157
+ ## watch - Explicit Control
113
158
  ```js
114
- onInit() {
115
- this.obj = [{id:1, name:'John Doe'}]
116
- }
117
-
118
- render() {
119
- return `${ watch(['obj'], () => this.obj.map( (e: any) =>
120
- html`<span>ID:${e.id} Name:${e.name}</span>`)
121
- ) }`
159
+ // Fine-grained control over dependencies
160
+ render() {
161
+ return html`
162
+ ${watch(['count', 'theme'], () => html`
163
+ <div class="${this.theme}">
164
+ Count: ${this.count}
165
+ </div>
166
+ `)}
167
+ `;
122
168
  }
123
169
  ```
124
170
 
125
- ## For VSCode Syntax Highlight template literals
126
171
 
127
- ### Install extension [inline-html](https://marketplace.visualstudio.com/items?itemName=pushqrdx.inline-html)
172
+ ## Examples
128
173
 
129
- ```js
130
- render() {
131
- return html`<div>with syntax highlighted</div>`
132
- }
133
- ```
174
+ Check out these live examples demonstrating RedGin's capabilities:
134
175
 
176
+ ### Basic Examples
177
+ * [Simple Counter](https://github.com/josnin/redgin/tree/Dev/samples) - Getting started with RedGin
178
+ * [Two-way Data Binding](https://github.com/josnin/redgin/tree/Dev/samples) - Using getset and events
179
+ * [Todo App](https://github.com/josnin/redgin/tree/Dev/samples) - Classic todo example
135
180
 
136
181
 
137
- ## Reference
138
- https://web.dev/custom-elements-best-practices/
182
+ ### Style Examples
183
+
184
+ * [Bootstrap Integration](https://github.com/josnin/redgin/tree/Dev/samples) - Using shareStyle with CSS frameworks
185
+ * [Design Tokens](https://github.com/josnin/redgin/tree/Dev/samples) - Global theme variables
186
+ * [Scoped Styles](https://github.com/josnin/redgin/tree/Dev/samples) - Component-specific CSS
139
187
 
140
- https://web.dev/shadowdom-v1/
141
188
 
189
+ ### Advanced Patterns
190
+ * [Surgical List Updates (1,000+ items)](https://github.com/josnin/redgin/tree/Dev/samples) - Only updated rows re-render
191
+ * [E-commerce Application](https://github.com/josnin/redgin/tree/Dev/samples) - Cart, checkout, and async operations
192
+ * [CRM Dashboard](https://github.com/josnin/redgin/tree/Dev/samples) - Multi-view with modals and pipeline
193
+ * [Parent-Child Communication](https://github.com/josnin/redgin/tree/Dev/samples) - Custom events and props
142
194
 
143
- ## How to run development server?
195
+ ### Integration Examples
196
+
197
+ * [TypeScript Support](https://github.com/josnin/redgin/tree/Dev/samples) - Full type safety
198
+ * [With Bootstrap](https://github.com/josnin/redgin/tree/Dev/samples) - Using CSS frameworks
199
+ * [Property Reflection](https://github.com/josnin/redgin/tree/Dev/samples) - Syncing props with attributes
200
+
201
+ ## Performance
202
+
203
+ * Surgical Updates: Only changed elements re-render
204
+ * Bundle Size: ~5.3kb minified + gzipped
205
+ * Memory: Zero virtual DOM overhead
206
+ * Style Injection: Global styles shared once, not duplicated per component
207
+
208
+ ## Contributing
209
+
210
+ We welcome contributions!
144
211
  ```
145
- git clone git@github.com:josnin/redgin.git
146
- cd ~/Documents/redgin/
212
+ git clone https://github.com/josnin/redgin.git
213
+ cd redgin
147
214
  npm install
148
215
  npm run dev
149
216
  ```
150
217
 
218
+ ## Reference
219
+ https://web.dev/custom-elements-best-practices/
220
+
221
+ https://web.dev/shadowdom-v1/
222
+
223
+
151
224
  ## Help
152
225
 
153
226
  Need help? Open an issue in: [ISSUES](https://github.com/josnin/redgin/issues)
@@ -156,3 +229,4 @@ Need help? Open an issue in: [ISSUES](https://github.com/josnin/redgin/issues)
156
229
  ## Contributing
157
230
  Want to improve and add feature? Fork the repo, add your changes and send a pull request.
158
231
 
232
+
@@ -0,0 +1,11 @@
1
+ /**
2
+ * watchProp: Specifically for component attributes/props.
3
+ * Returns a marker string that the RedGin core parses as a "Directive on a Tag".
4
+ */
5
+ /**
6
+ * Type definition for the reactive expression.
7
+ * 'this' is typed as the component instance.
8
+ */
9
+ type WatchExpression<T = any> = (this: T) => any;
10
+ export declare const attr: (attrName: string, exp: WatchExpression) => string;
11
+ export {};
@@ -1,4 +1,16 @@
1
- export declare const event: (type: string, fn: any) => string;
1
+ export type EventHandler = (this: any, e: any) => any;
2
+ export declare const event: (type: string, fn: EventHandler) => string;
3
+ /**
4
+ * Alias for 'event'. Moving forward, use 'on' for a more
5
+ * declarative feel (e.g., on('click', ...))
6
+ */
7
+ export declare const on: (type: string, fn: EventHandler) => string;
2
8
  export declare function emit(this: any, customEvent: string, value: any, options?: CustomEvent): void;
9
+ /**
10
+ * Attach all events for this component
11
+ */
3
12
  export declare function applyEventListeners(this: any): void;
13
+ /**
14
+ * Remove all events for this component
15
+ */
4
16
  export declare function removeEventListeners(this: any): void;
@@ -1,3 +1,5 @@
1
1
  export * from './watch';
2
+ export * from './stream';
3
+ export * from './attr';
2
4
  export * from './events';
3
5
  export * from './directives';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Type definition for the reactive expression.
3
+ * 'this' is typed as the component instance.
4
+ */
5
+ type WatchExpression<T = any> = (this: T) => any;
6
+ /**
7
+ * The 's' (stream) utility: Provides "Auto-Tracked" reactivity.
8
+ *
9
+ * Unlike standard 'watch', it treats the expression as a dynamic stream.
10
+ * It surgically extracts dependencies via regex from the function source,
11
+ * removing the need for manual reference arrays.
12
+ *
13
+ * It creates a placeholder <in-watch> and registers it to the component
14
+ * instance for O(1) targeted updates.
15
+ */
16
+ export declare const s: (exp: WatchExpression) => string;
17
+ export {};
@@ -1,2 +1,10 @@
1
- export declare const watch: (ref: string[], exp: any) => string;
2
- export declare function watchFn(this: any, _prop: string): boolean;
1
+ /**
2
+ * Type definition for the reactive expression.
3
+ * 'this' is typed as the component instance.
4
+ */
5
+ export type WatchExpression<T = any> = (this: T) => any;
6
+ /**
7
+ * The 'watch' utility used inside render().
8
+ * It creates a placeholder and registers dependencies to the current component instance.
9
+ */
10
+ export declare const watch: (refs: string[], exp?: WatchExpression) => string;
@@ -1,5 +1,13 @@
1
+ /**
2
+ * Options for the getset behavior.
3
+ * forWatch: determines if changing this property should trigger a DOM update.
4
+ */
1
5
  interface IGetSet {
2
6
  forWatch?: boolean;
3
7
  }
8
+ /**
9
+ * Factory function to mark a property as reactive state.
10
+ * Usage: myData = getset(0)
11
+ */
4
12
  export declare function getset<T>(value: T, options?: IGetSet): T;
5
13
  export {};
@@ -1,8 +1,16 @@
1
+ /**
2
+ * Interface for propReflect options.
3
+ * Allows custom conversion logic and explicit type declarations.
4
+ */
1
5
  interface IPropReflect<T = any> {
2
6
  serializerFn?: (this: any, prop: string, type: any, _default: T) => T;
3
7
  deserializerFn?: (this: any, prop: string, type: any, _default: T, value: T) => void;
4
8
  type?: any;
5
9
  name?: string;
6
10
  }
11
+ /**
12
+ * Marks a property for reflection.
13
+ * Usage: myProp = propReflect(false)
14
+ */
7
15
  export declare function propReflect<T>(value: T, options?: IPropReflect<T>): T;
8
16
  export {};
package/dist/redgin.d.ts CHANGED
@@ -1,25 +1,88 @@
1
- export { event, emit, watch, customDirectives } from './directives/index';
1
+ import { WatchExpression } from './directives/index';
2
+ export { on, event, // to obsolete
3
+ emit, s, watch, attr, customDirectives, } from './directives/index';
2
4
  export { getset, propReflect, customPropsBehavior } from './props/index';
3
- export declare const attachShadow: ShadowRootInit;
4
- export declare let injectStyles: string[];
5
- export declare let defaultStyles: string[];
6
- export declare const html: (raw: TemplateStringsArray, ...values: any[]) => string;
7
- export declare const css: (raw: TemplateStringsArray, ...values: any[]) => string;
5
+ export declare const shared: string[];
6
+ export declare const defaultStyle = ":host{display:block}";
7
+ /**
8
+ * Handles style injection with support for adoptedStyleSheets (faster memory sharing)
9
+ * and standard <style>/<link> fallbacks.
10
+ */
11
+ export declare function _applyStyle(styles: string | string[], shadowRoot?: ShadowRoot): string;
12
+ /**
13
+ * Add global styles that will be applied to every RedGin component
14
+ */
15
+ export declare function shareStyle(style: string): void;
16
+ /**
17
+ * THE SURGICAL FLATTENER:
18
+ * 1. Recursively flattens arrays.
19
+ * 2. Joins with EMPTY STRING '' (kills the comma). --> Note: watcher do this
20
+ * 3. Filters out 'dead' values (null, undefined, false).
21
+ */
22
+ export declare const _f: (v: any) => string;
23
+ export declare const html: (raw: TemplateStringsArray, ...vals: any[]) => string;
24
+ export declare const safe: (val: any) => string;
25
+ export declare const css: (raw: TemplateStringsArray, ...vals: any[]) => string;
8
26
  export declare class RedGin extends HTMLElement {
27
+ private _pending;
28
+ private _changed;
29
+ private _connected;
30
+ private _reactiveCache;
31
+ /**
32
+ * INSTANCE-LEVEL CACHING
33
+ * These Maps allow O(1) lookups for data-binding.
34
+ * We store direct references to HTMLElements so we never use querySelector during updates.
35
+ */
36
+ _watchRegistry: Map<string, Map<string, WatchExpression<any>>>;
37
+ _idToProps: Map<string, string[]>;
38
+ _watchElements: Map<string, HTMLElement>;
39
+ _attrRegistry: Map<string, Map<string, WatchExpression<any>>>;
40
+ _attrElements: Map<string, HTMLElement>;
41
+ _eventElements: Map<string, HTMLElement>;
42
+ styles: string[];
9
43
  constructor();
10
44
  connectedCallback(): void;
11
- attributeChangedCallback(prop: any, oldValue: any, newValue: any): void;
12
45
  disconnectedCallback(): void;
13
- private updateContents;
14
- private setEventListeners;
15
- private setPropsBehavior;
16
- getStyles(styles: string[]): string;
17
- private _onInit;
18
- private _onDoUpdate;
19
- private _onUpdated;
46
+ attributeChangedCallback(prop: string, oldV: any, newV: any): void;
47
+ /**
48
+ * Schedules a DOM update using a Microtask.
49
+ * If 5 properties change at once, only 1 DOM update is triggered.
50
+ */
51
+ protected requestUpdate(prop: string): void;
52
+ /**
53
+ * The "Tick" where DOM updates actually happen.
54
+ */
55
+ private _flush;
56
+ /**
57
+ * Initial setup: Sets up props, applies styles, renders HTML, and caches DOM nodes.
58
+ */
59
+ private _init;
60
+ private _collectElements;
61
+ /**
62
+ * Garbage collection: Removes watcher references when an <in-watch> element is removed.
63
+ */
64
+ _cleanupWatch(uniqId: string): void;
65
+ /**
66
+ * First-time synchronization of property values to DOM.
67
+ */
68
+ private _sync;
69
+ /**
70
+ * Core update logic: Calls registered directives (like watchFn)
71
+ */
72
+ private _update;
73
+ /**
74
+ * Lifecycle hook triggered after DOM updates are finished.
75
+ */
76
+ private _afterUpdate;
77
+ private _afterUpdateNoDomChange;
78
+ /**
79
+ * Identifies all class properties to be made reactive.
80
+ * Caches the list to avoid repeat CPU-heavy property reflection.
81
+ */
82
+ private _setupProps;
83
+ private _reactiveProps;
20
84
  onInit(): void;
21
85
  onDoUpdate(): void;
22
86
  onUpdated(): void;
23
- styles: string[];
24
87
  render(): string;
25
88
  }
@@ -1,13 +1,2 @@
1
- var O=Object.defineProperty;var $=(s,t,e)=>t in s?O(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var f=(s,t,e)=>($(s,typeof t!="symbol"?t+"":t,e),e);var d=()=>crypto.randomUUID().split("-")[0],v=s=>s.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`),u=s=>s.replace(/-./g,t=>t[1].toUpperCase());function x(s){let t=[];for(let e of h.reg)t.push(e.call(this,s));return t.filter(e=>e===!0).length>0}var y=class{static define(t){y.reg.push(t)}},h=y;f(h,"reg",[]);var p={},m=class extends HTMLElement{};customElements.get("in-watch")||customElements.define("in-watch",m);var L=(s,t)=>{let e=document.createElement("in-watch"),o=d();for(let r of s)Object.hasOwn(p,r)||(p[r]={}),p[r][o]=t;return e.dataset.watch__=o,e.outerHTML};function _(s){let t=u(s),e=!1;if(Object.hasOwn(p,t)){for(let o of Object.keys(p[t]))if(this.shadowRoot){let r=this.shadowRoot.querySelector(`[data-watch__="${o}"]`);r&&(r.innerHTML=p[t][o]?p[t][o].call(this):this[t],e=!0)}}return e}h.define(_);var S=[];var C=(s,t)=>{let e=d();return S.push([s,t,e]),`data-evt__=${e}`};function I(s,t,e){let o={detail:t,composed:!0},r=new CustomEvent(s,{...o,...e});this.shadowRoot&&this.shadowRoot.dispatchEvent(r)}function E(s){for(let t of S){let[e,o,r]=t;if(this.shadowRoot){let i=this.shadowRoot.querySelector(`[data-evt__="${r}"]`);i&&(s===0?i.addEventListener(e,o):i.removeEventListener(e,o))}}}function R(){E.call(this,0)}function T(){E.call(this,1)}function U(s,t){for(let e of c.reg)e.call(this,s,t)}var b=class{static define(t){b.reg.push(t)}},c=b;f(c,"reg",[]);var D=["^class$","^style$","^className$","^classList$","^id$","^dataset$","^data-","^aria-"],g=["disabled"],P=s=>{let t=!0;for(let e of D){let o=new RegExp(e,"g");if(s.match(o)){t=!1,console.error(`Please remove attribute '${s}' in the observedAttributes,
2
- DOM already provided built-in props reflection for this attribute.`);break}}return t};function j(s,t){if(t===void 0||t?.name!="propReflect")return;let{type:e,value:o,serializerFn:r,deserializerFn:i}=t,l=this.constructor.observedAttributes,w=u(s),n=v(s);if(l===void 0||!l.includes(n)){console.error(`Unable to apply propReflect '${w}' for attribute '${n}',
3
- Please add '${n}' in the observedAttributes of ${this.constructor.name} component`);return}!P(n)||Object.defineProperty(this,w,{configurable:!0,set(a){if(i)return i.call(this,n,e,o,a);(e===Boolean||typeof a=="boolean"||g.includes(n))&&a===!0?this.setAttribute(n,""):(e===Boolean||g.includes(n))&&a===!1?this.removeAttribute(n):([Object,Array].includes(e)||["object","array"].includes(typeof a))&&a?this.setAttribute(n,JSON.stringify(a)):([String,Number].includes(e)||["string","number"].includes(typeof a))&&a?this.setAttribute(n,a):this.removeAttribute(n)},get(){if(r)return r.call(this,n,e,o);if(n in g||e===Boolean||typeof o=="boolean")return this.hasAttribute(n);if(([String,Array,Object].includes(e)||["number","string","array","object"].includes(typeof o))&&!this.hasAttribute(n))return o;if((e===String||typeof o=="string")&&this.hasAttribute(n))return this.getAttribute(n);if((e===Number||typeof o=="number")&&this.hasAttribute(n))return Number(this.getAttribute(n));if(([Array,Object].includes(e)||["array","object"].includes(typeof o))&&this.hasAttribute(n))return JSON.parse(this.getAttribute(n))}})}function k(s,t){return{value:s,...t,name:"propReflect"}}c.define(j);function B(s,t){if(t===void 0||t?.name!="getset")return;let{value:e,forWatch:o}=t;this[`#${s}`]=e,Object.defineProperty(this,s,{configurable:!0,set(r){this[`#${s}`]=r,o&&this.updateContents(s)&&this._onUpdated()},get(){return this[`#${s}`]}})}function M(s,t){return{value:s,...{forWatch:!0},...t,name:"getset"}}c.define(B);var N={mode:"open",delegatesFocus:!0},q=[],F=[` /* Custom elements are display: inline by default,
4
- * so setting their width or height will have no effect
5
- */
6
- :host { display: block; }
7
- `],H=(s,...t)=>String.raw({raw:s},...t),bt=H,A=class extends HTMLElement{constructor(){super(),this.attachShadow(N)}connectedCallback(){this._onInit(),this._onDoUpdate()}attributeChangedCallback(t,e,o){if(e===o)return;this.updateContents(t)&&this._onUpdated()}disconnectedCallback(){T.call(this)}updateContents(t){return x.call(this,t)}setEventListeners(){R.call(this)}setPropsBehavior(){let t=Object.getOwnPropertyNames(this).filter(e=>e!="styles");for(let e of t){let o=this[e];U.call(this,e,o)}}getStyles(t){let e=[],o=[],r=this.shadowRoot?.adoptedStyleSheets;for(let i of t)if(i.startsWith("<link"))e.push(i);else if(i.startsWith("@import")||!r){let l=document.createElement("style");l.innerHTML=i,e.push(l.outerHTML)}else{let l=new CSSStyleSheet;l.replaceSync(i),o.push(l)}return this.shadowRoot&&o.length>0&&(this.shadowRoot.adoptedStyleSheets=[...this.shadowRoot.adoptedStyleSheets,...o]),e.join("")}_onInit(){this.setPropsBehavior(),this.shadowRoot&&(this.shadowRoot.innerHTML=`
8
- ${this.getStyles(q)}
9
- ${this.getStyles(F)}
10
- ${this.getStyles(this.styles)}
11
- ${this.render()}
12
- `),this.onInit()}_onDoUpdate(){let t=Object.getOwnPropertyNames(this).filter(e=>e!="styles");for(let e of t)this.updateContents(e)&&this._onUpdated();this.setEventListeners(),this.onDoUpdate()}_onUpdated(){this.setEventListeners(),this.onUpdated()}onInit(){}onDoUpdate(){}onUpdated(){}styles=[];render(){return""}};export{A as RedGin,N as attachShadow,bt as css,h as customDirectives,c as customPropsBehavior,F as defaultStyles,I as emit,C as event,M as getset,H as html,q as injectStyles,k as propReflect,L as watch};
1
+ var U=Object.defineProperty;var k=(n,t,e)=>t in n?U(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var g=(n,t,e)=>(k(n,typeof t!="symbol"?t+"":t,e),e);function d(){return"id-"+Math.random().toString(16).slice(2)+"-"+Date.now()}var S=n=>n.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`),u=n=>n.replace(/-./g,t=>t[1].toUpperCase());function T(n){let t=[];for(let e of p.reg)t.push(e.call(this,n));return t.filter(e=>e===!0).length>0}var m=class{static define(t){m.reg.push(t)}},p=m;g(p,"reg",[]);var q=(n,t)=>{let e=d(),s=window.__redgin_current_instance;if(s){for(let r=0;r<n.length;r++){let a=n[r],c=s._watchRegistry.get(a);c||(c=new Map,s._watchRegistry.set(a,c)),c.set(e,t)}s._idToProps.set(e,n)}return`<in-watch data-watch="${e}"></in-watch>`},y=class extends HTMLElement{disconnectedCallback(){let t=this.dataset.watch,s=this.getRootNode()?.host;t&&s&&s._cleanupWatch(t)}};customElements.get("in-watch")||customElements.define("in-watch",y);var A=n=>Array.isArray(n)?n.map(A).join(""):n===void 0?"":String(n);p.define(function(t){let e=u(t),s=this._watchRegistry.get(e);if(!s)return!1;let r=!1;for(let[a,c]of s){let i=this._watchElements.get(a);if((!i||!i.isConnected)&&(i=this.shadowRoot.querySelector(`[data-watch="${a}"]`),i&&this._watchElements.set(a,i)),i){let o=c?c.call(this):this[e],l=A(o);i.innerHTML!==String(l)&&(i.innerHTML=l,r=!0)}}return r});var I=n=>{let t=d(),e=window.__redgin_current_instance;if(e){let s=n.toString(),r=/this\.([a-zA-Z_$][\w$]*)/g,a,c=new Set;for(;(a=r.exec(s))!==null;){let i=a[1];e._reactiveCache?.includes(i)&&c.add(i)}for(let i of c){let o=e._watchRegistry.get(i);o||(o=new Map,e._watchRegistry.set(i,o)),o.set(t,n)}e._idToProps.set(t,Array.from(c))}return`<in-watch data-watch="${t}"></in-watch>`};var W=(n,t)=>{let e=d(),s=window.__redgin_current_instance;if(s){let r=t.toString(),a=/this\.([a-zA-Z_$][\w$]*)/g,c,i=new Set;for(;(c=a.exec(r))!==null;){let o=c[1];s._reactiveCache&&s._reactiveCache.includes(o)&&i.add(o)}for(let o of i){s._attrRegistry||(s._attrRegistry=new Map);let l=s._attrRegistry.get(o);l||(l=new Map,s._attrRegistry.set(o,l)),l.set(e,{attrName:n,exp:t})}}return`data-attr_${n}="${e}"`};p.define(function(t){let e=u(t),s=this._attrRegistry?.get(e);if(!s)return!1;let r=!1;for(let[a,c]of s){let i=this._attrElements.get(a);if((!i||!i.isConnected)&&(i=this.shadowRoot.querySelector(`[data-attr_${c.attrName}="${a}"]`),i&&this._attrElements.set(a,i)),i){let o=c.exp?c.exp.call(this):this[e],l=c.attrName;if(o===!1||o===null||o===void 0)i.hasAttribute(l)&&(i.removeAttribute(l),r=!0);else{let _=o===!0?"":String(o);i.getAttribute(l)!==_&&(i.setAttribute(l,_),r=!0)}}}return r});var $=(n,t)=>{let e=d(),s=window.__redgin_current_instance;return s&&(s._eventRegistry||(s._eventRegistry=new Map),s._eventRegistry.set(e,[n,t])),`data-evt__="${e}"`},L=$;function O(n,t,e){let s={detail:t,composed:!0},r=new CustomEvent(n,{...s,...e});this.shadowRoot&&this.shadowRoot.dispatchEvent(r)}function w(){if(!(!this._eventElements||!this._eventRegistry))for(let[n,t]of this._eventElements){let e=this._eventRegistry.get(n);if(!e)continue;let[s,r]=e;t.addEventListener(s,r)}}function C(){if(!(!this._eventElements||!this._eventRegistry))for(let[n,t]of this._eventElements){let e=this._eventRegistry.get(n);if(!e)continue;let[s,r]=e;t.removeEventListener(s,r)}}function P(n,t){for(let e of f.reg)e.call(this,n,t)}var b=class{static define(t){b.reg.push(t)}},f=b;g(f,"reg",[]);var D=["^class$","^style$","^className$","^classList$","^id$","^dataset$","^data-","^aria-","^hidden$","^tabindex$","^slot$","^title$","^contenteditable$","^draggable$","^spellcheck$"],F=D.map(n=>new RegExp(n)),j=new Set(["disabled","hidden","checked","selected","readonly","multiple"]),H=n=>{for(let t of F)if(t.test(n))return console.error(`Please remove attribute '${n}' from observedAttributes; browser already provides built-in reflection.`),!1;return!0};function z(n,t){if(!t||t.name!=="propReflect")return;let{type:e,value:s,serializerFn:r,deserializerFn:a}=t,c=this.constructor.observedAttributes,i=u(n),o=S(n);if(!c?.includes(o)){console.error(`propReflect '${i}' cannot map to '${o}'. Add '${o}' to observedAttributes of ${this.constructor.name}.`);return}if(!H(o))return;let l=e===Boolean||j.has(o)||typeof s=="boolean",_=e===Object||e===Array||["object","array"].includes(typeof s),R=e===String||e===Number||["string","number"].includes(typeof s);Object.defineProperty(this,i,{configurable:!0,set(h){if(a)return a.call(this,o,e,s,h);l?h?this.setAttribute(o,""):this.removeAttribute(o):_&&h!=null?this.setAttribute(o,JSON.stringify(h)):R&&h!=null?this.setAttribute(o,h):this.removeAttribute(o)},get(){if(r)return r.call(this,o,e,s);if(l)return this.hasAttribute(o);if(!this.hasAttribute(o))return s;if(_)try{return JSON.parse(this.getAttribute(o))}catch{return s}if(R){let h=this.getAttribute(o);return e===Number?Number(h):h}return this.getAttribute(o)}})}function B(n,t){return{value:n,...t,name:"propReflect"}}f.define(z);function G(n,t){if(t===void 0||t?.name!="getset")return;let{value:e,forWatch:s}=t,r=`#${n}`;this[r]=e,Object.defineProperty(this,n,{configurable:!0,set(a){this[r]!==a&&(this[r]=a,s&&this.requestUpdate(n))},get(){return this[r]}})}function K(n,t){return{value:n,...{forWatch:!0},...t,name:"getset"}}f.define(G);var M=new Map,E=[],Z=":host{display:block}";function v(n,t){let e=Array.isArray(n)?n:[n],s=[];if(!t)return e.join("");for(let r of e){if(r.startsWith("<link")){let a=r.match(/href="([^"]+)"/)?.[1];if(a&&!t.querySelector(`link[href="${a}"]`)){let c=document.createElement("div");c.innerHTML=r;let i=c.firstElementChild;i&&t.appendChild(i)}continue}if("adoptedStyleSheets"in t){let a=M.get(r);a||(a=new CSSStyleSheet,a.replaceSync(r),M.set(r,a)),t.adoptedStyleSheets=[...t.adoptedStyleSheets,a];continue}s.push(`<style>${r}</style>`)}return s.join("")}function Mt(n){E.includes(n)||E.push(n)}var x=n=>Array.isArray(n)?n.map(x).join(""):n===void 0?"":String(n),J=(n,...t)=>n.reduce((e,s,r)=>e+s+x(t[r]),""),V={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"},Nt=n=>x(n).replace(/[&<>"']/g,e=>V[e]),Ut=J,N=class extends HTMLElement{_pending=!1;_changed=new Set;_connected=!1;_reactiveCache=[];_watchRegistry=new Map;_idToProps=new Map;_watchElements=new Map;_attrRegistry=new Map;_attrElements=new Map;_eventElements=new Map;styles=[];constructor(){super(),this.attachShadow({mode:"open",delegatesFocus:!0})}connectedCallback(){this._connected||(this._connected=!0,this._init())}disconnectedCallback(){C.call(this)}attributeChangedCallback(t,e,s){e!==s&&this.requestUpdate(t)}requestUpdate(t){this._changed.add(t),!this._pending&&(this._pending=!0,queueMicrotask(()=>this._flush()))}_flush(){if(this._pending=!1,!this._changed.size)return;let t=Array.from(this._changed);this._changed.clear();let e=!1;window.__redgin_current_instance=this;for(let s of t)this._update(s)&&(e=!0);window.__redgin_current_instance=null,e?this._afterUpdate():this._afterUpdateNoDomChange()}_init(){this._setupProps(),window.__redgin_current_instance=this,this.shadowRoot&&(v(E,this.shadowRoot),v(Z,this.shadowRoot),v(this.styles,this.shadowRoot),this.shadowRoot.innerHTML+=this.render()),this._collectElements(),this.onInit(),this._sync(),window.__redgin_current_instance=null}_collectElements(t="all"){if(!this.shadowRoot)return;t==="all"&&(this._watchElements.clear(),this._attrElements.clear()),this._eventElements.clear();let e=document.createTreeWalker(this.shadowRoot,NodeFilter.SHOW_ELEMENT,null),s;for(;s=e.nextNode();){let r=s.attributes;for(let a=0;a<r.length;a++){let{name:c,value:i}=r[a];c==="data-evt__"?this._eventElements.set(i,s):t==="all"&&(c==="data-watch"?this._watchElements.set(i,s):c.startsWith("data-attr__")&&this._attrElements.set(i,s))}}}_cleanupWatch(t){let e=this._idToProps.get(t);if(e){for(let s of e){let r=this._watchRegistry.get(s);r&&(r.delete(t),r.size||this._watchRegistry.delete(s))}this._idToProps.delete(t),this._watchElements.delete(t)}}_sync(){for(let t of this._reactiveProps())this._update(t);this._collectElements(),w.call(this),this.onDoUpdate()}_update(t){return T.call(this,t)}_afterUpdate(){this._collectElements("events"),w.call(this),this.onUpdated()}_afterUpdateNoDomChange(){this.onUpdated()}_setupProps(){if(!this._reactiveCache.length){let t=new Set(["styles","_pending","_changed","_connected"]);this._reactiveCache=Object.getOwnPropertyNames(this).filter(e=>!t.has(e))}for(let t of this._reactiveCache)P.call(this,t,this[t])}_reactiveProps(){return this._reactiveCache}onInit(){}onDoUpdate(){}onUpdated(){}render(){return""}};export{N as RedGin,v as _applyStyle,x as _f,W as attr,Ut as css,p as customDirectives,f as customPropsBehavior,Z as defaultStyle,O as emit,$ as event,K as getset,J as html,L as on,B as propReflect,I as s,Nt as safe,Mt as shareStyle,E as shared,q as watch};
13
2
  //# sourceMappingURL=redgin.min.js.map
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare const getUniqID: () => string;
1
+ export declare function getUniqID(): string;
2
2
  export declare const camelToKebab: (str: string) => string;
3
3
  export declare const kebabToCamel: (str: string) => string;
package/index.d.ts CHANGED
@@ -0,0 +1,19 @@
1
+ import './samples/sample-styles';
2
+ import './samples/sample-binding';
3
+ import './samples/sampleIf';
4
+ import './samples/sample-nested';
5
+ import './samples/sample-allevents';
6
+ import './samples/ecommerce-app';
7
+ import './samples/todo-catalog';
8
+ import './samples/smart-list';
9
+ import './samples/smart-update-list';
10
+ import './samples/smart-pagination-list';
11
+ import './samples/smart-sort-list';
12
+ import './samples/xss-sandbox';
13
+ import './samples/parentChild/parentChild';
14
+ import './samples/childParent/cart-manager';
15
+ import './samples/modal-app';
16
+ import './samples/master-binding';
17
+ import './samples/smart-grid';
18
+ import './samples/crm-app';
19
+ import './samples/check-search-list';
package/package.json CHANGED
@@ -1,11 +1,39 @@
1
1
  {
2
2
  "name": "redgin",
3
- "version": "0.1.18",
4
- "description": "~5.3kb Simplified library for building Web Components, works on Vanilla JS / all JS framework",
3
+ "version": "0.2.1",
4
+ "description": "A lightweight (~5.3kb) library for building Web Components with surgical updates and fine-grained reactivity",
5
5
  "keywords": [
6
- "redgin",
7
6
  "web components",
8
- "custom elements"
7
+ "custom elements",
8
+ "reactive",
9
+ "lightweight",
10
+ "tiny",
11
+ "reactive web components",
12
+ "custom elements library",
13
+ "web components framework",
14
+ "shadow dom",
15
+ "vanilla js",
16
+ "no dependencies",
17
+ "reactive programming",
18
+ "fine-grained reactivity",
19
+ "surgical updates",
20
+ "template literals",
21
+ "UI library",
22
+ "component library",
23
+ "frontend",
24
+ "browser",
25
+ "javascript",
26
+ "typescript",
27
+ "reactive state",
28
+ "state management",
29
+ "reactive properties",
30
+ "attribute reflection",
31
+ "custom events",
32
+ "lifecycle hooks",
33
+ "SPA",
34
+ "single page application",
35
+ "micro frontend",
36
+ "framework agnostic"
9
37
  ],
10
38
  "main": "dist/redgin.min.js",
11
39
  "types": "dist/redgin.d.ts",
@@ -19,7 +47,7 @@
19
47
  "build": "npm run clean && tsc -w",
20
48
  "bundle": "npx esbuild --bundle src/redgin.js --minify --sourcemap --format=esm --outfile=dist/redgin.min.js --target=es2022 && npm run copy",
21
49
  "copy": "cp -rvf src/*.d.ts dist/. && cp -rfv src/directives/*.d.ts dist/directives/. && cp -rfv src/props/*.d.ts dist/props/.",
22
- "dev": "node node_modules/vite/bin/vite.js"
50
+ "dev": "node node_modules/vite/bin/vite.js --host 0.0.0.0"
23
51
  },
24
52
  "repository": {
25
53
  "type": "git",
@@ -32,10 +60,10 @@
32
60
  },
33
61
  "homepage": "https://github.com/josnin/redgin#readme",
34
62
  "devDependencies": {
35
- "@types/node": "^18.11.14",
63
+ "@types/node": "^25.2.3",
36
64
  "esbuild": "^0.16.4",
37
65
  "rimraf": "^4.1.2",
38
66
  "typescript": "^4.9.5",
39
- "vite": "^4.1.1"
67
+ "vite": "^7.3.1"
40
68
  }
41
69
  }
package/vite.config.js ADDED
@@ -0,0 +1,5 @@
1
+ export default {
2
+ server: {
3
+ allowedHosts: ['myfedora.local'] // . prefix allows subdomains
4
+ }
5
+ }
@@ -1,5 +0,0 @@
1
- export declare function applyDirectives(this: any, prop: string): boolean;
2
- export declare class customDirectives {
3
- static reg: any;
4
- static define(d: any): void;
5
- }
@@ -1,4 +0,0 @@
1
- export declare const event: (type: string, fn: any) => string;
2
- export declare function emit(this: any, customEvent: string, value: any, options?: CustomEvent): void;
3
- export declare function applyEventListeners(this: any): void;
4
- export declare function removeEventListeners(this: any): void;
@@ -1,2 +0,0 @@
1
- export declare const watch: (ref: string[], exp: any) => string;
2
- export declare function watchFn(this: any, _prop: string): boolean;