@ynck/rendux 0.92.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 +19 -0
- package/EULA-COMMERCIAL.md +32 -0
- package/LICENSE-NONCOMMERCIAL.md +11 -0
- package/README.md +465 -0
- package/dist/plugin/i18n.cjs.min.js +2 -0
- package/dist/plugin/i18n.min.js +2 -0
- package/dist/plugin/x-logger.cjs.min.js +2 -0
- package/dist/plugin/x-logger.min.js +2 -0
- package/dist/plugin/x-tooltip.cjs.min.js +2 -0
- package/dist/plugin/x-tooltip.min.js +2 -0
- package/dist/rendux.cjs.min.js +2 -0
- package/dist/rendux.min.js +2 -0
- package/package.json +39 -0
- package/rendux-logo.jpg +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Changelog
|
|
2
|
+
|
|
3
|
+
### v0.92
|
|
4
|
+
- Mini Evaluator is now directly embeded in `rendux`
|
|
5
|
+
|
|
6
|
+
### v0.91
|
|
7
|
+
- Added conditional logging system for plugins and core features
|
|
8
|
+
- Removed x-click-outside plugin
|
|
9
|
+
- Fixed plugin attribute handling and re-rendering
|
|
10
|
+
- Improved plugin logging control via `logs` attribute
|
|
11
|
+
|
|
12
|
+
### v0.86
|
|
13
|
+
- Inlined plugin core into `rendux.js`, removing dependence on external raw-render-core.js.
|
|
14
|
+
- Added chainable plugin API: `.use()`, `.process()`, `plugins`, and `parsePluginCall` directly on `rendux`.
|
|
15
|
+
- Fixed plugin attribute lookup to avoid invalid CSS selector issues for `render.plugin`.
|
|
16
|
+
|
|
17
|
+
### v0.84
|
|
18
|
+
- Hidden elements (`x-if`) now reside in an in-memory `DocumentFragment` instead of a `<template>`, preventing hidden nodes from cluttering the DOM or shadow DOM.
|
|
19
|
+
- `<template x-for>` loops are now tracked from both the live DOM and the hidden fragment for correct re-rendering.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Commercial End User License Agreement (EULA)
|
|
2
|
+
|
|
3
|
+
This Commercial EULA ("Agreement") is a legal contract between you ("Licensee") and Yannick J.A. CHARLERY ("Licensor"). By obtaining, installing, or using the software (the "Software"), Licensee agrees to be bound by the terms below.
|
|
4
|
+
|
|
5
|
+
1. License Grant.
|
|
6
|
+
Licensor hereby grants Licensee a limited, non-exclusive, non-transferable, worldwide license to use the Software for commercial purposes, subject to payment of the applicable license fee.
|
|
7
|
+
|
|
8
|
+
2. Restrictions.
|
|
9
|
+
Licensee shall not:
|
|
10
|
+
a) Copy, distribute, or sublicense the Software beyond the scope of this Agreement.
|
|
11
|
+
b) Reverse engineer, decompile, disassemble, modify, translate, adapt, or create derivative works of the Software.
|
|
12
|
+
c) Use the Software in violation of any applicable laws or regulations.
|
|
13
|
+
|
|
14
|
+
3. Maintenance & Support.
|
|
15
|
+
Any updates, bug fixes, or support services are provided at Licensor's discretion and may be subject to additional fees.
|
|
16
|
+
|
|
17
|
+
4. Termination.
|
|
18
|
+
This Agreement and the license granted shall terminate automatically if Licensee fails to comply with any term herein. Upon termination, Licensee must cease all use and destroy all copies of the Software.
|
|
19
|
+
|
|
20
|
+
5. Warranty Disclaimer.
|
|
21
|
+
The Software is provided "AS IS", without warranty of any kind, express or implied. Licensor expressly disclaims all warranties, including but not limited to merchantability and fitness for a particular purpose.
|
|
22
|
+
|
|
23
|
+
6. Limitation of Liability.
|
|
24
|
+
In no event shall Licensor be liable for any indirect, incidental, special, or consequential damages arising out of or related to the Software or this Agreement.
|
|
25
|
+
|
|
26
|
+
7. Governing Law.
|
|
27
|
+
This Agreement shall be governed by the laws of France, excluding its conflict-of-law rules and the CISG. Any dispute arising out of or in connection with this Agreement shall be finally settled under the Rules of Arbitration of the International Chamber of Commerce (ICC) by one arbitrator. The seat of arbitration shall be Paris, France, and the language of the arbitration shall be English. Judgment on the award may be entered in any court of competent jurisdiction.
|
|
28
|
+
|
|
29
|
+
8. Entire Agreement.
|
|
30
|
+
This Agreement constitutes the entire agreement between the parties and supersedes all prior or contemporaneous agreements.
|
|
31
|
+
|
|
32
|
+
For inquiries or to purchase a commercial license, contact: ynck.chrl@pm.me
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Non-Commercial Code License (Version 1.0)
|
|
2
|
+
|
|
3
|
+
Copyright (c) JULY 2025 Yannick J.A. CHARLERY
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use and copy the Software strictly for non-commercial purposes only. Licensee may not modify, translate, adapt, merge, publish, distribute, sublicense, or create derivative works of the Software.
|
|
6
|
+
|
|
7
|
+
Commercial use, sale, or distribution of the Software (in source or binary form) is expressly prohibited without prior written authorization and a separate commercial license.
|
|
8
|
+
|
|
9
|
+
For information on commercial licensing, please refer to the accompanying EULA-COMMERCIAL.md or contact: <ynck.chrl@pm.me>
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
Source-available (non‑commercial).
|
|
4
|
+
Commercial licenses via EULA (See EULA-COMMERCIAL.md for commercial use).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# rendux
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
|
|
11
|
+
`rendux` is a lightweight dependency-free templating engine designed for use within Custom Elements (Web Components). It provides reactive templating features through directives, making it easy to create dynamic, state-driven components without a full framework.
|
|
12
|
+
|
|
13
|
+
### Size
|
|
14
|
+
|
|
15
|
+
- Minified: 13.0 KB (ESM), 13.0 KB (CJS)
|
|
16
|
+
- Gzipped: 4.7 KB
|
|
17
|
+
|
|
18
|
+
## Core Concepts
|
|
19
|
+
|
|
20
|
+
`rendux` is called within a Custom Element's context and operates on its shadow DOM. It provides several directives to handle common UI patterns:
|
|
21
|
+
|
|
22
|
+
- `x-if` for conditional rendering
|
|
23
|
+
- `x-for` for list rendering
|
|
24
|
+
- `x-class` for conditional classes
|
|
25
|
+
- `x-*` for dynamic attributes
|
|
26
|
+
- `render` for text content interpolation
|
|
27
|
+
- `@event` for event handling
|
|
28
|
+
- `render.plugin` for plugin execution
|
|
29
|
+
|
|
30
|
+
## Logging System
|
|
31
|
+
|
|
32
|
+
rendux includes a flexible logging system controlled by the `logs` attribute. You can enable logging for specific features:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!-- Enable all logs -->
|
|
36
|
+
<my-component logs="all"></my-component>
|
|
37
|
+
|
|
38
|
+
<!-- Enable specific feature logs -->
|
|
39
|
+
<my-component logs="for, if, attr, class, render, plugins"></my-component>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Available log categories:
|
|
43
|
+
- `plugins`: Plugin execution and results
|
|
44
|
+
- `for`: x-for loop processing
|
|
45
|
+
- `if`: x-if conditional rendering
|
|
46
|
+
- `attr`: x-attr attribute processing
|
|
47
|
+
- `class`: x-class processing
|
|
48
|
+
- `render`: Text content rendering
|
|
49
|
+
|
|
50
|
+
## Basic Usage
|
|
51
|
+
|
|
52
|
+
### With Custom Elements (Web Components)
|
|
53
|
+
|
|
54
|
+
#### With Shadow DOM
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
class MyElement extends HTMLElement {
|
|
58
|
+
constructor() {
|
|
59
|
+
super();
|
|
60
|
+
this.attachShadow({ mode: 'open' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Example component state
|
|
64
|
+
users = [
|
|
65
|
+
{ name: 'Alice', active: true },
|
|
66
|
+
{ name: 'Bob', active: false }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
connectedCallback() {
|
|
70
|
+
this.shadowRoot.innerHTML = `
|
|
71
|
+
<div>
|
|
72
|
+
<h2 render="title">User List</h2>
|
|
73
|
+
<template x-for="user of users">
|
|
74
|
+
<div x-class="(active, user.active)">
|
|
75
|
+
<span render="user.name"></span>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
rendux.call(this);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Without Shadow DOM (plain Light DOM)
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
import { rendux } from './src/rendux.js';
|
|
89
|
+
|
|
90
|
+
class MyElement extends HTMLElement {
|
|
91
|
+
// Example component state
|
|
92
|
+
title = 'User List';
|
|
93
|
+
users = [
|
|
94
|
+
{ name: 'Alice', active: true },
|
|
95
|
+
{ name: 'Bob', active: false }
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
connectedCallback() {
|
|
99
|
+
// Render directly into light DOM (no shadow root)
|
|
100
|
+
this.innerHTML = `
|
|
101
|
+
<div>
|
|
102
|
+
<h2 render="title"></h2>
|
|
103
|
+
<template x-for="user of users">
|
|
104
|
+
<div x-class="(active, user.active)">
|
|
105
|
+
<span render="user.name"></span>
|
|
106
|
+
<button @click="toggleUser(user)">Toggle</button>
|
|
107
|
+
</div>
|
|
108
|
+
</template>
|
|
109
|
+
</div>
|
|
110
|
+
`;
|
|
111
|
+
rendux.call(this); // uses 'this' as root in Light DOM
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
toggleUser(user) {
|
|
115
|
+
user.active = !user.active;
|
|
116
|
+
rendux.call(this); // re-render after state change
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### With Plain HTML
|
|
123
|
+
|
|
124
|
+
You can also use rendux with plain HTML by creating a context object:
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<!DOCTYPE html>
|
|
128
|
+
<html>
|
|
129
|
+
<head>
|
|
130
|
+
<script type="module">
|
|
131
|
+
import { rendux } from './src/rendux.js';
|
|
132
|
+
|
|
133
|
+
// Create a context object with your data and methods
|
|
134
|
+
const context = {
|
|
135
|
+
title: 'User List',
|
|
136
|
+
users: [
|
|
137
|
+
{ name: 'Alice', active: true },
|
|
138
|
+
{ name: 'Bob', active: false }
|
|
139
|
+
],
|
|
140
|
+
|
|
141
|
+
// Add methods if needed
|
|
142
|
+
toggleUser(user) {
|
|
143
|
+
user.active = !user.active;
|
|
144
|
+
rendux.call(this); // Re-render after state change
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Call rendux with the context
|
|
149
|
+
rendux.call(context);
|
|
150
|
+
</script>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div>
|
|
154
|
+
<h2 render="title">User List</h2>
|
|
155
|
+
<template x-for="user of users">
|
|
156
|
+
<div x-class="(active, user.active)">
|
|
157
|
+
<span render="user.name"></span>
|
|
158
|
+
<button @click="toggleUser(user)">Toggle</button>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
</div>
|
|
162
|
+
</body>
|
|
163
|
+
</html>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Key Points for Plain HTML Usage:**
|
|
167
|
+
- Create a context object with your data and methods
|
|
168
|
+
- Use `rendux.call(context)` to bind the context
|
|
169
|
+
- Call `rendux.call(context)` again after state changes to re-render
|
|
170
|
+
- The context object becomes the `this` reference in all expressions
|
|
171
|
+
|
|
172
|
+
### With CommonJS (Node.js)
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
// Import using CommonJS
|
|
176
|
+
const { rendux } = require('./dist/rendux.cjs');
|
|
177
|
+
|
|
178
|
+
// Create a context with your data
|
|
179
|
+
const context = {
|
|
180
|
+
title: 'Server Context',
|
|
181
|
+
users: [
|
|
182
|
+
{ name: 'Alice', active: true },
|
|
183
|
+
{ name: 'Bob', active: false }
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Note: rendux requires a DOM environment
|
|
188
|
+
// For Node.js, use with a DOM implementation like jsdom
|
|
189
|
+
const { JSDOM } = require('jsdom');
|
|
190
|
+
const dom = new JSDOM(`
|
|
191
|
+
<div>
|
|
192
|
+
<h2 render="title">User List</h2>
|
|
193
|
+
<template x-for="user of users">
|
|
194
|
+
<div x-class="(active, user.active)">
|
|
195
|
+
<span render="user.name"></span>
|
|
196
|
+
</div>
|
|
197
|
+
</template>
|
|
198
|
+
</div>
|
|
199
|
+
`);
|
|
200
|
+
|
|
201
|
+
// Set up global document for rendux
|
|
202
|
+
global.document = dom.window.document;
|
|
203
|
+
|
|
204
|
+
// Call rendux with the context
|
|
205
|
+
rendux.call(context, dom.window.document.body);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**When to Use `rendux.cjs`:**
|
|
209
|
+
|
|
210
|
+
Use the CommonJS build (`dist/rendux.cjs`) in these scenarios:
|
|
211
|
+
|
|
212
|
+
1. **Node.js Build Tools**: When using bundlers or build systems that require CommonJS (older Webpack configs, some testing frameworks)
|
|
213
|
+
2. **Server-Side Rendering (SSR)**: When rendering components on the server with a DOM implementation like jsdom or happy-dom
|
|
214
|
+
3. **Legacy Node.js Projects**: Projects that haven't migrated to ES modules (`type: "module"` in package.json)
|
|
215
|
+
4. **Testing Environments**: Jest or other test runners configured for CommonJS (though modern Jest supports ESM)
|
|
216
|
+
5. **npm Package Distribution**: When publishing packages that need to support both module systems
|
|
217
|
+
|
|
218
|
+
**Note**: rendux requires a DOM environment. For Node.js/server usage, you must provide a DOM implementation (jsdom, happy-dom, linkedom) since rendux uses `document`, `Element`, `querySelectorAll`, etc.
|
|
219
|
+
|
|
220
|
+
For modern projects with native ESM support, prefer the ESM build (`dist/rendux.js`) instead.
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## Directives
|
|
225
|
+
|
|
226
|
+
### 1. Conditional Rendering (`x-if`)
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<div x-if="someCondition">
|
|
230
|
+
Only shown when condition is true
|
|
231
|
+
</div>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The element is completely removed from the DOM (not just hidden) when the condition is false.
|
|
235
|
+
|
|
236
|
+
### 2. List Rendering (`x-for`)
|
|
237
|
+
|
|
238
|
+
Supports two syntaxes:
|
|
239
|
+
```html
|
|
240
|
+
<!-- Simple iteration -->
|
|
241
|
+
<template x-for="item of items">
|
|
242
|
+
<div render="item.name"></div>
|
|
243
|
+
</template>
|
|
244
|
+
|
|
245
|
+
<!-- With index -->
|
|
246
|
+
<template x-for="(item, index) of items">
|
|
247
|
+
<div render="index + ': ' + item.name"></div>
|
|
248
|
+
</template>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Features:
|
|
252
|
+
- Uses `<template>` tag to define the repeatable content
|
|
253
|
+
- Maintains its own render cache per iteration
|
|
254
|
+
- Supports deep change detection for arrays
|
|
255
|
+
- Provides iteration context to nested elements
|
|
256
|
+
|
|
257
|
+
### 3. Class Binding (`x-class`)
|
|
258
|
+
|
|
259
|
+
Two syntaxes available:
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<!-- Single class with condition -->
|
|
263
|
+
<div x-class="highlight, isActive">
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<!-- Multiple class/condition pairs -->
|
|
267
|
+
<div x-class="(selected, isSelected) (highlight, isHighlighted)">
|
|
268
|
+
</div>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 4. Dynamic Attributes (`x-*`)
|
|
272
|
+
|
|
273
|
+
Any attribute prefixed with `x-` (except special directives) becomes dynamic:
|
|
274
|
+
|
|
275
|
+
```html
|
|
276
|
+
<input x-value="inputValue">
|
|
277
|
+
<img x-src="imageUrl">
|
|
278
|
+
<div x-data-id="getId()">
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 5. Text Content (`render`)
|
|
282
|
+
|
|
283
|
+
Renders dynamic text content:
|
|
284
|
+
|
|
285
|
+
```html
|
|
286
|
+
<!-- Simple value -->
|
|
287
|
+
<span render="message"></span>
|
|
288
|
+
|
|
289
|
+
<!-- With condition -->
|
|
290
|
+
<span render="message, isVisible">
|
|
291
|
+
Default text when not visible
|
|
292
|
+
</span>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Features:
|
|
296
|
+
- Supports expressions
|
|
297
|
+
- Can access component methods and properties
|
|
298
|
+
- Handles objects (converts to JSON)
|
|
299
|
+
- Supports conditional rendering
|
|
300
|
+
|
|
301
|
+
### 6. Event Handling (`@event`)
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<!-- Click events -->
|
|
305
|
+
<button @click="handleClick()">Click me</button>
|
|
306
|
+
|
|
307
|
+
<!-- Form events -->
|
|
308
|
+
<input @input="updateValue(event.target.value)"
|
|
309
|
+
@focus="handleFocus()"
|
|
310
|
+
@blur="handleBlur()">
|
|
311
|
+
|
|
312
|
+
<!-- Keyboard events -->
|
|
313
|
+
<input @keyup="handleKeyUp(event)"
|
|
314
|
+
@keydown="handleKeyDown(event)">
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The event object is automatically available in handlers as `event`.
|
|
318
|
+
|
|
319
|
+
## Plugin System
|
|
320
|
+
|
|
321
|
+
rendux includes an integrated plugin system that allows you to extend templating functionality.
|
|
322
|
+
|
|
323
|
+
### Using Plugins
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
import { rendux } from './src/rendux.js';
|
|
327
|
+
import { i18nPlugin } from './src/plugins/plugin-i18n.js';
|
|
328
|
+
import { xTooltip } from './src/plugins/plugin-x-tooltip.js';
|
|
329
|
+
|
|
330
|
+
// Register plugins (chainable)
|
|
331
|
+
rendux
|
|
332
|
+
.use(i18nPlugin({
|
|
333
|
+
translations: {
|
|
334
|
+
en: { greeting: 'Hello!' },
|
|
335
|
+
fr: { greeting: 'Bonjour!' }
|
|
336
|
+
}
|
|
337
|
+
}))
|
|
338
|
+
.use(xTooltip);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Using Plugin Directives
|
|
342
|
+
|
|
343
|
+
```html
|
|
344
|
+
<!-- i18n Plugin -->
|
|
345
|
+
<p render.i18n="greeting">Hello!</p>
|
|
346
|
+
|
|
347
|
+
<!-- Tooltip Plugin -->
|
|
348
|
+
<button x-tooltip="'Click to save'">Save</button>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Building Custom Plugins
|
|
352
|
+
|
|
353
|
+
A plugin is an object with the following structure:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
export const myPlugin = {
|
|
357
|
+
// Required properties
|
|
358
|
+
name: 'pluginName', // Used in render.pluginName or x-pluginName
|
|
359
|
+
target: 'attribute', // Optional: 'attribute' for x-* plugins
|
|
360
|
+
execute(el, value, ctx) {
|
|
361
|
+
// Main plugin logic
|
|
362
|
+
return 'result'; // Optional: string to use as element content
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// Optional lifecycle hooks (in order of execution)
|
|
366
|
+
onBeforeRender(root, component) {
|
|
367
|
+
// Called ONCE at the start of rendux()
|
|
368
|
+
// Use for initialization, setup, or capturing initial state
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
onBeforeExecute(el, rawValue, ctx) {
|
|
372
|
+
// Called BEFORE each plugin execution on an element
|
|
373
|
+
// Use for element-specific setup or validation
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
onAfterExecute(el, rawValue, ctx) {
|
|
377
|
+
// Called AFTER each plugin execution on an element
|
|
378
|
+
// Use for cleanup or post-processing of element changes
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
onAfterRender(root, ctx) {
|
|
382
|
+
// Called ONCE at the end of rendux()
|
|
383
|
+
// Use for final cleanup or post-render operations
|
|
384
|
+
// This hook is async - can return a Promise
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Security
|
|
390
|
+
|
|
391
|
+
rendux is designed with security as a core principle, implementing several protective measures to prevent common web vulnerabilities:
|
|
392
|
+
|
|
393
|
+
### XSS Prevention Through Safe Content Handling
|
|
394
|
+
|
|
395
|
+
**No innerHTML Usage for Dynamic Content**
|
|
396
|
+
- rendux never uses `innerHTML` for dynamic content rendering
|
|
397
|
+
- All user data is inserted using `textContent`, which automatically escapes HTML
|
|
398
|
+
- This prevents malicious script injection through user-controlled data
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
// Safe: User input is escaped automatically
|
|
402
|
+
<span render="userInput"></span> // Uses textContent internally
|
|
403
|
+
|
|
404
|
+
// Unsafe (what rendux avoids):
|
|
405
|
+
element.innerHTML = userInput; // Could execute scripts
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Sandboxed Expression Evaluator
|
|
409
|
+
|
|
410
|
+
**Custom Mini-Evaluator Instead of eval()**
|
|
411
|
+
- rendux includes a custom expression parser that never uses `eval()` or `Function()`
|
|
412
|
+
- Only supports safe operations: property access, method calls, basic operators
|
|
413
|
+
- Prevents arbitrary code execution through template expressions
|
|
414
|
+
|
|
415
|
+
**Whitelisted Global Access**
|
|
416
|
+
- Only safe global objects are accessible: `Math`, `Date`, `Number`, `String`, `Boolean`, `JSON`, `Array`
|
|
417
|
+
- No access to dangerous globals like `window`, `document`, or `eval`
|
|
418
|
+
- Controlled execution environment limits potential attack vectors
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
// Safe expressions supported:
|
|
422
|
+
render="user.name" ✓
|
|
423
|
+
render="Math.max(a, b)" ✓
|
|
424
|
+
render="items.length > 0 ? 'Yes' : 'No'" ✓
|
|
425
|
+
|
|
426
|
+
// Dangerous expressions blocked:
|
|
427
|
+
render="eval('malicious code')" ✗ (eval not accessible)
|
|
428
|
+
render="window.location = 'evil.com'" ✗ (window not accessible)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Template Content Security
|
|
432
|
+
|
|
433
|
+
**Safe Template Processing**
|
|
434
|
+
- Template elements (`<template x-for>`) use `.content` property, not innerHTML
|
|
435
|
+
- Plugin system processes attributes safely without executing embedded scripts
|
|
436
|
+
- Event handlers are properly scoped and don't use string-to-function conversion
|
|
437
|
+
|
|
438
|
+
### Best Practices for Secure Usage
|
|
439
|
+
|
|
440
|
+
1. **Validate Plugin Inputs**: When creating custom plugins, validate and sanitize inputs
|
|
441
|
+
2. **Context Isolation**: Each component maintains its own isolated context
|
|
442
|
+
3. **No Script Injection**: User data in expressions is evaluated safely, not executed as code
|
|
443
|
+
|
|
444
|
+
```js
|
|
445
|
+
// Safe plugin example
|
|
446
|
+
export const safePlugin = {
|
|
447
|
+
name: 'format',
|
|
448
|
+
execute(value) {
|
|
449
|
+
// Validate and sanitize input
|
|
450
|
+
if (typeof value !== 'string') return '';
|
|
451
|
+
return value.replace(/[<>&"']/g, char => ({
|
|
452
|
+
'<': '<', '>': '>', '&': '&',
|
|
453
|
+
'"': '"', "'": '''
|
|
454
|
+
})[char]);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
This security model makes rendux suitable for applications handling user-generated content while maintaining the flexibility of a templating engine.
|
|
460
|
+
|
|
461
|
+
## License
|
|
462
|
+
|
|
463
|
+
This project is available under two licenses:
|
|
464
|
+
- Non-commercial use: See LICENSE-NONCOMMERCIAL.md
|
|
465
|
+
- Commercial use: See EULA-COMMERCIAL.md
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/i18n v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={en:{message:"Welcome to rendux!",button:"Click me",title:"Hello World"},fr:{message:"Bienvenue dans rendux!",button:"Cliquez-moi",title:"Bonjour le Monde"}};const t=function(t={}){const n=t.translations||e;let o=t.defaultLanguage||"en";return{name:"i18n",target:"render",execute:function(e,...t){const r=e.split(".");let s=n[o];for(const t of r){if(!s||"object"!=typeof s||!(t in s))return`[${e}]`;s=s[t]}if("string"==typeof s&&t.length>0){return s.replace(/\{(\d+)\}/g,(e,n)=>{const o=parseInt(n,10);return void 0!==t[o]?t[o]:e})}return s||`[${e}]`},setLanguage:e=>!!n[e]&&(o=e,!0),getLanguage:()=>o,addTranslations(e,t){n[e]||(n[e]={}),Object.assign(n[e],t)}}};exports.i18nPlugin=t;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/i18n v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
const e={en:{message:"Welcome to rendux!",button:"Click me",title:"Hello World"},fr:{message:"Bienvenue dans rendux!",button:"Cliquez-moi",title:"Bonjour le Monde"}};const t=function(t={}){const n=t.translations||e;let o=t.defaultLanguage||"en";return{name:"i18n",target:"render",execute:function(e,...t){const r=e.split(".");let s=n[o];for(const t of r){if(!s||"object"!=typeof s||!(t in s))return`[${e}]`;s=s[t]}if("string"==typeof s&&t.length>0){return s.replace(/\{(\d+)\}/g,(e,n)=>{const o=parseInt(n,10);return void 0!==t[o]?t[o]:e})}return s||`[${e}]`},setLanguage:e=>!!n[e]&&(o=e,!0),getLanguage:()=>o,addTranslations(e,t){n[e]||(n[e]={}),Object.assign(n[e],t)}}};export{t as i18nPlugin};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/x-logger v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={name:"logger",target:"render",execute:function(e,...r){switch(e){case"logMessage":return`[Message: ${r.join(", ")}]`;case"logError":return`[Error: ${r.join(", ")}]`;case"logWarning":return`[Warning: ${r.join(", ")}]`;case"logInfo":return`[Info: ${r.join(", ")}]`;case"logDebug":return`[Debug: ${r.join(", ")}]`;case"logTable":return r.length>0&&(Array.isArray(r[0])||"object"==typeof r[0])?`[Table: ${Array.isArray(r[0])?r[0].length+" items":"object"}]`:`[Table: ${r.join(", ")}]`;default:return`[${e}: ${r.join(", ")}]`}}},r=e;exports.logger=e,exports.loggerPlugin=r;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/x-logger v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
const e={name:"logger",target:"render",execute:function(e,...r){switch(e){case"logMessage":return`[Message: ${r.join(", ")}]`;case"logError":return`[Error: ${r.join(", ")}]`;case"logWarning":return`[Warning: ${r.join(", ")}]`;case"logInfo":return`[Info: ${r.join(", ")}]`;case"logDebug":return`[Debug: ${r.join(", ")}]`;case"logTable":return r.length>0&&(Array.isArray(r[0])||"object"==typeof r[0])?`[Table: ${Array.isArray(r[0])?r[0].length+" items":"object"}]`:`[Table: ${r.join(", ")}]`;default:return`[${e}: ${r.join(", ")}]`}}},r=e;export{e as logger,r as loggerPlugin};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/x-tooltip v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={name:"x-tooltip",target:"attribute",execute:function(e,t,o){e._xTooltipElement&&e._xTooltipElement.remove();const n=document.createElement("div");n.textContent=t,n.style.cssText="\n position: absolute;\n background: #333;\n color: white;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n ",document.body.appendChild(n),e._xTooltipElement=n;const i=function(t){const o=e.getBoundingClientRect(),i=window.pageXOffset||document.documentElement.scrollLeft,s=window.pageYOffset||document.documentElement.scrollTop,l=i+o.left+(o.width-n.offsetWidth)/2,p=s+o.top-n.offsetHeight-8;n.style.left=`${l}px`,n.style.top=`${p}px`,n.style.opacity="1"},s=function(){n.style.opacity="0"};if(e._xTooltipListeners&&(e.removeEventListener("mouseenter",e._xTooltipListeners.show),e.removeEventListener("mouseleave",e._xTooltipListeners.hide)),e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",s),e._xTooltipListeners={show:i,hide:s},!e._xTooltipCleanup){e._xTooltipCleanup=!0;const t=new MutationObserver(o=>{o.forEach(o=>{o.removedNodes.forEach(o=>{(o===e||o.contains&&o.contains(e))&&(e._xTooltipElement&&e._xTooltipElement.remove(),t.disconnect())})})});t.observe(document.body,{childList:!0,subtree:!0})}}};exports.xTooltip=e;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! plugin/x-tooltip v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
const e={name:"x-tooltip",target:"attribute",execute:function(e,t,o){e._xTooltipElement&&e._xTooltipElement.remove();const n=document.createElement("div");n.textContent=t,n.style.cssText="\n position: absolute;\n background: #333;\n color: white;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 12px;\n white-space: nowrap;\n z-index: 1000;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n ",document.body.appendChild(n),e._xTooltipElement=n;const i=function(t){const o=e.getBoundingClientRect(),i=window.pageXOffset||document.documentElement.scrollLeft,s=window.pageYOffset||document.documentElement.scrollTop,l=i+o.left+(o.width-n.offsetWidth)/2,p=s+o.top-n.offsetHeight-8;n.style.left=`${l}px`,n.style.top=`${p}px`,n.style.opacity="1"},s=function(){n.style.opacity="0"};if(e._xTooltipListeners&&(e.removeEventListener("mouseenter",e._xTooltipListeners.show),e.removeEventListener("mouseleave",e._xTooltipListeners.hide)),e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",s),e._xTooltipListeners={show:i,hide:s},!e._xTooltipCleanup){e._xTooltipCleanup=!0;const t=new MutationObserver(o=>{o.forEach(o=>{o.removedNodes.forEach(o=>{(o===e||o.contains&&o.contains(e))&&(e._xTooltipElement&&e._xTooltipElement.remove(),t.disconnect())})})});t.observe(document.body,{childList:!0,subtree:!0})}}};export{e as xTooltip};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! rendux v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function a(){return n[o++]||{type:"EOF"}}function i(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return a(),r}function l(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;l("||");){a();const t=f();e=e||t}return e}();if(l("?")){a();const t=u();i("operator",":");const r=u();e=e?t:r}return e}function f(){let e=p();for(;l("&&");){a();const t=p();e=e&&t}return e}function p(){let e=d();for(;["==","!=","===","!=="].includes(s().value);){const t=a().value,r=d();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function d(){let e=h();for(;[">","<",">=","<="].includes(s().value);){const t=a().value,r=h();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function h(){let e=x();for(;["+","-"].includes(s().value);){const t=a().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=a().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=a().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return a(),t.value;if("identifier"===t.type){a();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(l(".")){a();const e=i("identifier").value;n=null==n?void 0:n[e]}else if(l("[")){a();const e=u();i("operator","]"),n=null==n?void 0:n[e]}else{if(!l("("))break;{a();const e=[];if(!l(")"))do{e.push(u())}while(l(",")&&a());if(i("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(l("(")){a();const e=u();return i("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const a=n[2].trim();if(""===a)return{plugin:s,args:[]};return{plugin:s,args:a.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,a=n.value.trim();let i,l,c;if("render.plugin"===s){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else{const e=s.slice(7);if(i=r.get(e),!i)continue;if(a.startsWith(e+"(")){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else l=[a]}"function"==typeof i.onBeforeExecute&&i.onBeforeExecute(e,a,t);try{c=i.execute(...l)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof i.onAfterExecute&&i.onAfterExecute(e,a,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e=this.shadowRoot||this){const n=this;for(const t of r.values())"function"==typeof t.onBeforeRender&&t.onBeforeRender(e,n);const o=document,a=n.getAttribute("logs"),i=Boolean(a),l=a?new Set(a.split(",").map(e=>e.trim().toLowerCase())):new Set,c=e=>l.has("all")||l.has(e.toLowerCase())||l.has(("x-"+e).toLowerCase()),u=i&&l.has("plugins"),f=(...e)=>console.warn(...e);n._xRenderCache||(n._xRenderCache=new Map),n._xRenderOriginalText||(n._xRenderOriginalText=new WeakMap),n._xIfData||(n._xIfData=new WeakMap),n._cloneContext||(n._cloneContext=new WeakMap),n._xClassCache||(n._xClassCache=new Map),n._xForCache||(n._xForCache=new WeakMap),n.__mirrorContainer||(n.__mirrorContainer=o.createDocumentFragment());const p={};function d(e){let t=e;for(;t;){const e=n._cloneContext.get(t);if(e)return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",n),n}function h(e,r){if(!e)return!0;const n=d(r);try{return t(e,{this:n,...p})}catch(t){return void f(`[evaluate] "${e}" failed in`,n,t)}}Object.getOwnPropertyNames(Object.getPrototypeOf(n)).forEach(e=>{"function"==typeof n[e]&&"constructor"!==e&&(p[e]=n[e].bind(n))}),p.plugins=r;const x=i&&c("for");x&&console.groupCollapsed("x-for");Array.from(e.querySelectorAll("template[x-for]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("template[x-for]"))).forEach(e=>{let t,r,s;e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},n.__mirrorContainer.appendChild(e));try{({loopVar:t,indexVar:r,arrayPath:s}=function(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}(e.getAttribute("x-for")))}catch(e){return void f("[x-for]",e.message)}const a=function(e,t){const r=d(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}(s,e);if(!Array.isArray(a))return void f(`[x-for] expected array at ${s}`,a);x&&console.log(`iterating ${s} → length ${a.length}`);const i=function(e){return JSON.stringify(e)}(a),l=n._xForCache.get(e);l&&l.arrayRef===a&&l.length===a.length&&l.signature===i?x&&console.log(" → skipped (no change)"):(n._xForCache.set(e,{arrayRef:a,length:a.length,signature:i}),e._forClones&&e._forClones.forEach(e=>e.remove()),e._forClones=[],a.forEach((s,a)=>{x&&console.groupCollapsed(` index ${a}`);const i=o.createDocumentFragment();i.appendChild(e.content.cloneNode(!0));const l=(c=t,u=s,f=r,p=a,new Proxy(n,{has:(e,t)=>t===c||f&&t===f||t in e,get:(e,t)=>t===c?u:f&&t===f?p:e[t]}));var c,u,f,p,d,h;d=i,h=l,n._cloneContext.set(d,h),d.querySelectorAll("*").forEach(e=>n._cloneContext.set(e,h));const g=n._xRenderCache,y=n._xRenderOriginalText;for(n._xRenderCache=new Map,n._xRenderOriginalText=new WeakMap,rendux.call(n,i),n._xRenderCache=g,n._xRenderOriginalText=y;i.firstChild;){const t=i.firstChild;e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t)}x&&console.groupEnd()}))}),x&&console.groupEnd();const g=i&&c("if");Array.from(e.querySelectorAll("[x-if]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||h(t,e);g&&console.groupCollapsed("x-if",e,t,"→",r);let s=n._xIfData.get(e);s||(s={placeholder:o.createComment("x-if placeholder"),isHidden:!1},n._xIfData.set(e,s)),r||s.isHidden?r&&s.isHidden&&(s.placeholder.parentNode.replaceChild(e,s.placeholder),s.isHidden=!1):(e.parentNode.replaceChild(s.placeholder,e),n.__mirrorContainer.appendChild(e),s.isHidden=!0),g&&console.groupEnd()}),0===n._xRenderCache.size&&Array.from(e.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(n._xRenderCache.set(e,t),n._xRenderOriginalText.set(e,e.textContent))}),Array.from(e.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||n._xClassCache.has(e)||n._xClassCache.set(e,t)});const y=i&&c("attr");Array.from(e.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const o=t.name,s=r.get(o);if(s&&"attribute"===s.target){const r=h(t.value.trim(),e);try{u&&console.log("[plugin]",`Executing ${o} with:`,{element:e,value:r,component:n}),s.execute(e,r,n)}catch(e){u&&console.error("[plugin]",`Error executing ${o}:`,e)}}else{const r=t.name.slice(2),n=h(t.value.trim(),e);y&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),y&&console.groupEnd()}}})});const m=i&&c("class");m&&console.groupCollapsed("x-class");for(const[e,t]of n._xClassCache){m&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;n._xClassDynamicValues||(n._xClassDynamicValues=new Map);(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const o=new Set;let s;for(;s=t.exec(r);){const t=s[1].trim(),r=s[2].trim();let n;n=/^[\w\-\s]+$/.test(t)?t:h(t,e);const a=Boolean(h(r,e));let i=[];"string"==typeof n?i=n.split(/\s+/).filter(Boolean):Array.isArray(n)?i=n:n&&"object"==typeof n&&(i=Object.keys(n).filter(e=>n[e])),a&&i.forEach(t=>e.classList.add(t)),i.forEach(e=>o.add(e)),m&&console.log(` ${t} → [${i.join(" ")}] (= ${a})`)}n._xClassDynamicValues.set(e,Array.from(o))}else{n._xClassDynamicValues||(n._xClassDynamicValues=new Map);let t;(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let o=!0;if(!r.includes(",")&&/[?:]/.test(r))t=h(r,e);else{const n=r.indexOf(","),s=n>=0?r.slice(0,n).trim():r,a=n>=0?r.slice(n+1).trim():"true";o=Boolean(h(a,e)),t=s}const s=[];"string"==typeof t?s.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?s.push(...t):t&&"object"==typeof t&&s.push(...Object.keys(t).filter(e=>t[e])),s.forEach(t=>{o?e.classList.add(t):e.classList.remove(t)}),m&&console.log(` x-class ${r} →`,s,`ok=${o}`),n._xClassDynamicValues.set(e,s)}}m&&console.groupEnd();const _=i&&c("render");_&&console.groupCollapsed("render");for(const[e,t]of n._xRenderCache){const r=n._xIfData.get(e);if(r&&r.isHidden)continue;_&&console.log("element →",e,"expr=",t);const o=t.indexOf(","),s=o<0?t:t.slice(0,o).trim(),a=h(o<0?"true":t.slice(o+1).trim(),e);let i;if(a){let t=h(s,e);if(void 0===t)i=n._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{i=JSON.stringify(t,null,2)}catch{i=String(t)}else i=null==t||"boolean"==typeof t?"":String(t)}else i=n._xRenderOriginalText.get(e)||"";e.textContent!==i&&(e.textContent=i),_&&console.log(` → "${i}" (cond=${a})`)}_&&console.groupEnd(),Array.from(e.querySelectorAll("*")).forEach(e=>{const t=n._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!c(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const o=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[o]&&e.removeEventListener(o,e._xEventListeners[o]),e._xEventListeners||(e._xEventListeners={});const a=d(e),i=new Proxy(a,{get:(e,t)=>t in e?e[t]:n[t]}),l=function(e){try{return t(s,{this:i,event:e,...p})}catch(e){f(`[rendux @${o}] Error evaluating: ${s}`,e)}};e.addEventListener(o,l),e._xEventListeners[o]=l}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(e),s(e,n)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o}),exports.parsePluginCall=o,exports.plugins=r,exports.process=s,exports.rendux=rendux,exports.use=n;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! rendux v0.92.0 | Copyright (c) JULY 2025 Yannick J.A. CHARLERY | This software is licensed for non-commercial use only. Commercial licensing available upon request. For license details: https://github.com/ynck-chrl/rendux */
|
|
2
|
+
const e={Math:Math,Date:Date,Number:Number,String:String,Boolean:Boolean,JSON:JSON,parseInt:parseInt,parseFloat:parseFloat,isFinite:isFinite,Array:Array};function t(t,r={}){const n=function(e){const t=[];let r=0;for(;r<e.length;){let n=e[r];if(/\s/.test(n)){r++;continue}if('"'===n||"'"===n){const o=n;let s="";for(r++;r<e.length&&e[r]!==o;)"\\"===e[r]?(r++,r<e.length&&(s+=e[r++])):s+=e[r++];r++,t.push({type:"string",value:s});continue}if(/[0-9]/.test(n)||"."===n&&/[0-9]/.test(e[r+1])){let n=r;for(;r<e.length&&/[0-9]/.test(e[r]);)r++;if("."===e[r])for(r++;r<e.length&&/[0-9]/.test(e[r]);)r++;const o=parseFloat(e.slice(n,r));t.push({type:"number",value:o});continue}if(/[a-zA-Z_$]/.test(n)){let n=r;for(r++;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;const o=e.slice(n,r);"true"===o||"false"===o?t.push({type:"boolean",value:"true"===o}):"null"===o?t.push({type:"null",value:null}):t.push({type:"identifier",value:o});continue}const o=e.substr(r,2),s=e.substr(r,3);if(["===","!=="].includes(s))t.push({type:"operator",value:s}),r+=3;else if(["==","!=",">=","<=","&&","||"].includes(o))t.push({type:"operator",value:o}),r+=2;else{if(!["+","-","*","/","%",">","<","!","?",":","(",")","[","]",".",","].includes(n))throw new Error(`Invalid character '${n}' in expression`);t.push({type:"operator",value:n}),r++}}return t.push({type:"EOF"}),t}(t);let o=0;function s(){return n[o]||{type:"EOF"}}function a(){return n[o++]||{type:"EOF"}}function i(e,t){const r=s();if(r.type!==e||void 0!==t&&r.value!==t)throw new Error(`Expected ${t||e} but got ${r.value}`);return a(),r}function l(e){const t=s();return"operator"===t.type&&t.value===e}const c=u();if("EOF"!==s().type)throw new Error(`Unexpected token: ${s().value}`);return c;function u(){let e=function(){let e=f();for(;l("||");){a();const t=f();e=e||t}return e}();if(l("?")){a();const t=u();i("operator",":");const r=u();e=e?t:r}return e}function f(){let e=p();for(;l("&&");){a();const t=p();e=e&&t}return e}function p(){let e=d();for(;["==","!=","===","!=="].includes(s().value);){const t=a().value,r=d();switch(t){case"==":e=e==r;break;case"!=":e=e!=r;break;case"===":e=e===r;break;case"!==":e=e!==r}}return e}function d(){let e=h();for(;[">","<",">=","<="].includes(s().value);){const t=a().value,r=h();switch(t){case">":e=e>r;break;case"<":e=e<r;break;case">=":e=e>=r;break;case"<=":e=e<=r}}return e}function h(){let e=x();for(;["+","-"].includes(s().value);){const t=a().value,r=x();e="+"===t?e+r:e-r}return e}function x(){let e=g();for(;["*","/","%"].includes(s().value);){const t=a().value,r=g();switch(t){case"*":e*=r;break;case"/":e/=r;break;case"%":e%=r}}return e}function g(){if(["!","+","-"].includes(s().value)){const e=a().value,t=g();switch(e){case"!":return!t;case"+":return+t;case"-":return-t}}return function(){const t=s();if("number"===t.type||"string"===t.type||"boolean"===t.type||"null"===t.type)return a(),t.value;if("identifier"===t.type){a();let n=function(t){if(t in r)return r[t];if(t in e)return e[t];if(null!=r.this&&t in r.this)return r.this[t];throw new Error(`Unknown identifier: ${t}`)}(t.value);for(;;)if(l(".")){a();const e=i("identifier").value;n=null==n?void 0:n[e]}else if(l("[")){a();const e=u();i("operator","]"),n=null==n?void 0:n[e]}else{if(!l("("))break;{a();const e=[];if(!l(")"))do{e.push(u())}while(l(",")&&a());if(i("operator",")"),"function"!=typeof n)throw new Error(`'${t.value}' is not a function`);n=n.apply(r.this,e)}}return n}if(l("(")){a();const e=u();return i("operator",")"),e}throw new Error(`Unexpected token: ${t.value}`)}()}}const r=new Map;function n(e){if(!e||!e.name)throw new Error("Invalid plugin");r.set(e.name,e)}function o(e,t={}){const n=e.match(/^([a-zA-Z_$][\w$]*)\s*\(\s*(.*)\s*\)$/);if(!n)return null;const o=n[1],s=r.get(o);if(!s)return null;const a=n[2].trim();if(""===a)return{plugin:s,args:[]};return{plugin:s,args:a.split(/\s*,\s*/).map(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(/^-?\d+(?:\.\d+)?$/.test(e))return parseFloat(e);if(e in t)return t[e];throw new Error(`Unknown identifier: ${e}`)})}}async function s(e=document,t={}){const n=Array.from(e.querySelectorAll("*")).filter(e=>Array.from(e.attributes).some(e=>e.name.startsWith("render.")));for(const e of n)for(const n of Array.from(e.attributes)){if(!n.name.startsWith("render."))continue;const s=n.name,a=n.value.trim();let i,l,c;if("render.plugin"===s){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else{const e=s.slice(7);if(i=r.get(e),!i)continue;if(a.startsWith(e+"(")){let e;try{e=o(a,t)}catch(e){console.error(e);continue}if(!e)continue;i=e.plugin,l=e.args}else l=[a]}"function"==typeof i.onBeforeExecute&&i.onBeforeExecute(e,a,t);try{c=i.execute(...l)}catch(e){continue}e.textContent=null==c?"":String(c),"function"==typeof i.onAfterExecute&&i.onAfterExecute(e,a,t)}for(const n of r.values())"function"==typeof n.onAfterRender&&await n.onAfterRender(e,t)}function rendux(e=this.shadowRoot||this){const n=this;for(const t of r.values())"function"==typeof t.onBeforeRender&&t.onBeforeRender(e,n);const o=document,a=n.getAttribute("logs"),i=Boolean(a),l=a?new Set(a.split(",").map(e=>e.trim().toLowerCase())):new Set,c=e=>l.has("all")||l.has(e.toLowerCase())||l.has(("x-"+e).toLowerCase()),u=i&&l.has("plugins"),f=(...e)=>console.warn(...e);n._xRenderCache||(n._xRenderCache=new Map),n._xRenderOriginalText||(n._xRenderOriginalText=new WeakMap),n._xIfData||(n._xIfData=new WeakMap),n._cloneContext||(n._cloneContext=new WeakMap),n._xClassCache||(n._xClassCache=new Map),n._xForCache||(n._xForCache=new WeakMap),n.__mirrorContainer||(n.__mirrorContainer=o.createDocumentFragment());const p={};function d(e){let t=e;for(;t;){const e=n._cloneContext.get(t);if(e)return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Found context for element:",t,e),e;t=t.parentNode}return n.getAttribute("logs")&&n.getAttribute("logs").includes("plugins")&&console.log("Using default component context:",n),n}function h(e,r){if(!e)return!0;const n=d(r);try{return t(e,{this:n,...p})}catch(t){return void f(`[evaluate] "${e}" failed in`,n,t)}}Object.getOwnPropertyNames(Object.getPrototypeOf(n)).forEach(e=>{"function"==typeof n[e]&&"constructor"!==e&&(p[e]=n[e].bind(n))}),p.plugins=r;const x=i&&c("for");x&&console.groupCollapsed("x-for");Array.from(e.querySelectorAll("template[x-for]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("template[x-for]"))).forEach(e=>{let t,r,s;e._xForMeta||(e._xForMeta={parent:e.parentNode,next:e.nextSibling},n.__mirrorContainer.appendChild(e));try{({loopVar:t,indexVar:r,arrayPath:s}=function(e){const t=e.match(/^\s*(?:\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*\)|([^,\s()]+))\s+(?:in|of)\s+(.+)$/);if(!t)throw new Error("Invalid x-for: "+e);return{loopVar:t[1]||t[3],indexVar:t[2]||null,arrayPath:t[4].trim()}}(e.getAttribute("x-for")))}catch(e){return void f("[x-for]",e.message)}const a=function(e,t){const r=d(t),n=e.match(/([^[.\]]+)|\[(\d+)\]/g);if(n)return n.reduce((e,t)=>{if(null!=e){if(t.startsWith("[")){const r=parseInt(t.slice(1,-1),10);return Array.isArray(e)?e[r]:void 0}return e[t]}},r)}(s,e);if(!Array.isArray(a))return void f(`[x-for] expected array at ${s}`,a);x&&console.log(`iterating ${s} → length ${a.length}`);const i=function(e){return JSON.stringify(e)}(a),l=n._xForCache.get(e);l&&l.arrayRef===a&&l.length===a.length&&l.signature===i?x&&console.log(" → skipped (no change)"):(n._xForCache.set(e,{arrayRef:a,length:a.length,signature:i}),e._forClones&&e._forClones.forEach(e=>e.remove()),e._forClones=[],a.forEach((s,a)=>{x&&console.groupCollapsed(` index ${a}`);const i=o.createDocumentFragment();i.appendChild(e.content.cloneNode(!0));const l=(c=t,u=s,f=r,p=a,new Proxy(n,{has:(e,t)=>t===c||f&&t===f||t in e,get:(e,t)=>t===c?u:f&&t===f?p:e[t]}));var c,u,f,p,d,h;d=i,h=l,n._cloneContext.set(d,h),d.querySelectorAll("*").forEach(e=>n._cloneContext.set(e,h));const g=n._xRenderCache,y=n._xRenderOriginalText;for(n._xRenderCache=new Map,n._xRenderOriginalText=new WeakMap,rendux.call(n,i),n._xRenderCache=g,n._xRenderOriginalText=y;i.firstChild;){const t=i.firstChild;e._xForMeta.parent.insertBefore(t,e._xForMeta.next),e._forClones.push(t)}x&&console.groupEnd()}))}),x&&console.groupEnd();const g=i&&c("if");Array.from(e.querySelectorAll("[x-if]")).concat(Array.from(n.__mirrorContainer.querySelectorAll("[x-if]"))).forEach(e=>{const t=e.getAttribute("x-if")?.trim(),r=!t||h(t,e);g&&console.groupCollapsed("x-if",e,t,"→",r);let s=n._xIfData.get(e);s||(s={placeholder:o.createComment("x-if placeholder"),isHidden:!1},n._xIfData.set(e,s)),r||s.isHidden?r&&s.isHidden&&(s.placeholder.parentNode.replaceChild(e,s.placeholder),s.isHidden=!1):(e.parentNode.replaceChild(s.placeholder,e),n.__mirrorContainer.appendChild(e),s.isHidden=!0),g&&console.groupEnd()}),0===n._xRenderCache.size&&Array.from(e.querySelectorAll("[render]")).forEach(e=>{const t=e.getAttribute("render")?.trim();t&&(n._xRenderCache.set(e,t),n._xRenderOriginalText.set(e,e.textContent))}),Array.from(e.querySelectorAll("[x-class]")).forEach(e=>{const t=e.getAttribute("x-class");null==t||n._xClassCache.has(e)||n._xClassCache.set(e,t)});const y=i&&c("attr");Array.from(e.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(t=>{if(t.name.startsWith("x-")&&!["x-for","x-key","x-class","x-if","x-hidden"].includes(t.name)){const o=t.name,s=r.get(o);if(s&&"attribute"===s.target){const r=h(t.value.trim(),e);try{u&&console.log("[plugin]",`Executing ${o} with:`,{element:e,value:r,component:n}),s.execute(e,r,n)}catch(e){u&&console.error("[plugin]",`Error executing ${o}:`,e)}}else{const r=t.name.slice(2),n=h(t.value.trim(),e);y&&console.groupCollapsed("x-attr",e,r,"=",n),n?e.setAttribute(r,String(n)):e.removeAttribute(r),y&&console.groupEnd()}}})});const m=i&&c("class");m&&console.groupCollapsed("x-class");for(const[e,t]of n._xClassCache){m&&console.log("element →",e);const r=t.trim();if(r.includes("(")){const t=/\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)/g;n._xClassDynamicValues||(n._xClassDynamicValues=new Map);(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));const o=new Set;let s;for(;s=t.exec(r);){const t=s[1].trim(),r=s[2].trim();let n;n=/^[\w\-\s]+$/.test(t)?t:h(t,e);const a=Boolean(h(r,e));let i=[];"string"==typeof n?i=n.split(/\s+/).filter(Boolean):Array.isArray(n)?i=n:n&&"object"==typeof n&&(i=Object.keys(n).filter(e=>n[e])),a&&i.forEach(t=>e.classList.add(t)),i.forEach(e=>o.add(e)),m&&console.log(` ${t} → [${i.join(" ")}] (= ${a})`)}n._xClassDynamicValues.set(e,Array.from(o))}else{n._xClassDynamicValues||(n._xClassDynamicValues=new Map);let t;(n._xClassDynamicValues.get(e)||[]).forEach(t=>e.classList.remove(t));let o=!0;if(!r.includes(",")&&/[?:]/.test(r))t=h(r,e);else{const n=r.indexOf(","),s=n>=0?r.slice(0,n).trim():r,a=n>=0?r.slice(n+1).trim():"true";o=Boolean(h(a,e)),t=s}const s=[];"string"==typeof t?s.push(...t.split(/\s+/).filter(Boolean)):Array.isArray(t)?s.push(...t):t&&"object"==typeof t&&s.push(...Object.keys(t).filter(e=>t[e])),s.forEach(t=>{o?e.classList.add(t):e.classList.remove(t)}),m&&console.log(` x-class ${r} →`,s,`ok=${o}`),n._xClassDynamicValues.set(e,s)}}m&&console.groupEnd();const _=i&&c("render");_&&console.groupCollapsed("render");for(const[e,t]of n._xRenderCache){const r=n._xIfData.get(e);if(r&&r.isHidden)continue;_&&console.log("element →",e,"expr=",t);const o=t.indexOf(","),s=o<0?t:t.slice(0,o).trim(),a=h(o<0?"true":t.slice(o+1).trim(),e);let i;if(a){let t=h(s,e);if(void 0===t)i=n._xRenderOriginalText.get(e)||"";else if(null!=t&&"object"==typeof t)try{i=JSON.stringify(t,null,2)}catch{i=String(t)}else i=null==t||"boolean"==typeof t?"":String(t)}else i=n._xRenderOriginalText.get(e)||"";e.textContent!==i&&(e.textContent=i),_&&console.log(` → "${i}" (cond=${a})`)}_&&console.groupEnd(),Array.from(e.querySelectorAll("*")).forEach(e=>{const t=n._xIfData.get(e);t&&t.isHidden||[["render","render"]].forEach(([t,r])=>{e.hasAttribute(t)&&!c(r)&&e.removeAttribute(t)})}),function e(r){Array.from(r.querySelectorAll("*")).forEach(e=>{Array.from(e.attributes).forEach(r=>{if(r.name.startsWith("@")){const o=r.name.slice(1),s=r.value.trim();e._xEventListeners&&e._xEventListeners[o]&&e.removeEventListener(o,e._xEventListeners[o]),e._xEventListeners||(e._xEventListeners={});const a=d(e),i=new Proxy(a,{get:(e,t)=>t in e?e[t]:n[t]}),l=function(e){try{return t(s,{this:i,event:e,...p})}catch(e){f(`[rendux @${o}] Error evaluating: ${s}`,e)}};e.addEventListener(o,l),e._xEventListeners[o]=l}})}),Array.from(r.querySelectorAll("slot")).forEach(t=>{(t.assignedElements?t.assignedElements({flatten:!0}):[]).forEach(t=>{e(t)})})}(e),s(e,n)}rendux.use=function(e){return n(e),rendux},"undefined"!=typeof module&&module.exports&&(module.exports={rendux:rendux,use:n,process:s,plugins:r,parsePluginCall:o});export{o as parsePluginCall,r as plugins,s as process,rendux,n as use};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ynck/rendux",
|
|
3
|
+
"version": "0.92.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/rendux.cjs.min.js",
|
|
6
|
+
"module": "./dist/rendux.min.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/rendux.min.js",
|
|
10
|
+
"require": "./dist/rendux.cjs.min.js"
|
|
11
|
+
},
|
|
12
|
+
"./plugin/x-tooltip": {
|
|
13
|
+
"import": "./dist/plugin/x-tooltip.min.js",
|
|
14
|
+
"require": "./dist/plugin/x-tooltip.cjs.min.js"
|
|
15
|
+
},
|
|
16
|
+
"./plugin/i18n": {
|
|
17
|
+
"import": "./dist/plugin/i18n.min.js",
|
|
18
|
+
"require": "./dist/plugin/i18n.cjs.min.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"description": "A lightweight dependency-free templating and rendering library for HTML and Web Components",
|
|
22
|
+
"author": "Yannick J.A. CHARLERY",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/ynck-chrl/rendux"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/ynck-chrl/rendux",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/ynck-chrl/rendux/issues"
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"license": "SEE LICENSE IN LICENSE-NONCOMMERCIAL.md",
|
|
36
|
+
"files": [
|
|
37
|
+
"**/*"
|
|
38
|
+
]
|
|
39
|
+
}
|
package/rendux-logo.jpg
ADDED
|
Binary file
|