@symbiotejs/symbiote 3.5.7 → 3.6.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/AI_REFERENCE.md +33 -9
- package/README.md +39 -34
- package/core/Symbiote.js +54 -1
- package/core/itemizeProcessor-keyed.js +10 -1
- package/core/itemizeProcessor.js +4 -1
- package/core/itemizeSetup.js +3 -1
- package/core/warn.js +17 -2
- package/package.json +1 -1
- package/types/core/Symbiote.d.ts +2 -0
- package/types/core/Symbiote.d.ts.map +1 -1
- package/types/core/itemizeProcessor-keyed.d.ts.map +1 -1
- package/types/core/itemizeProcessor.d.ts.map +1 -1
- package/types/core/itemizeSetup.d.ts +1 -0
- package/types/core/itemizeSetup.d.ts.map +1 -1
- package/types/core/warn.d.ts +1 -0
- package/types/core/warn.d.ts.map +1 -1
package/AI_REFERENCE.md
CHANGED
|
@@ -441,7 +441,7 @@ class MyList extends Symbiote {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
MyList.template = html`
|
|
444
|
-
<ul
|
|
444
|
+
<ul itemize="items">
|
|
445
445
|
<template>
|
|
446
446
|
<li>
|
|
447
447
|
<span>{{name}}</span> - <span>{{role}}</span>
|
|
@@ -452,17 +452,41 @@ MyList.template = html`
|
|
|
452
452
|
`;
|
|
453
453
|
```
|
|
454
454
|
|
|
455
|
-
> **CRITICAL**: Inside itemize
|
|
456
|
-
>
|
|
457
|
-
>
|
|
458
|
-
>
|
|
459
|
-
>
|
|
455
|
+
> **CRITICAL**: Inside itemize, each item is a Symbiote component with its own state scope.
|
|
456
|
+
> There are **two patterns** — they determine how event handlers are bound:
|
|
457
|
+
>
|
|
458
|
+
> **Pattern 1: Inline `<template>` (dumb items)** — items have no class definition, only data properties from the array.
|
|
459
|
+
> Any event handler or data must come from an **external context** — not from the item itself.
|
|
460
|
+
> All context prefixes work: `^` (parent pop-up), `APP/` (named), `*` (shared). The most common is `^`:
|
|
461
|
+
> ```html
|
|
462
|
+
> <ul itemize="items">
|
|
463
|
+
> <template>
|
|
464
|
+
> <li>{{name}} <button ${{onclick: '^onItemClick'}}>Click</button></li>
|
|
465
|
+
> </template>
|
|
466
|
+
> </ul>
|
|
467
|
+
> ```
|
|
468
|
+
> Without a context prefix, the binding looks for the handler on the item itself — which doesn't have it, so it breaks.
|
|
469
|
+
>
|
|
470
|
+
> **Pattern 2: Custom `item-tag` (smart items)** — items are full components with their own class, templates, and methods.
|
|
471
|
+
> Handlers defined on the item component itself do **NOT** need `^`:
|
|
472
|
+
> ```html
|
|
473
|
+
> <div itemize="items" item-tag="my-item"></div>
|
|
474
|
+
> ```
|
|
475
|
+
> ```js
|
|
476
|
+
> class MyItem extends Symbiote {
|
|
477
|
+
> onItemClick() { console.log(this.$.name); }
|
|
478
|
+
> }
|
|
479
|
+
> MyItem.template = html`<li>{{name}} <button ${{onclick: 'onItemClick'}}>Click</button></li>`;
|
|
480
|
+
> MyItem.reg('my-item');
|
|
481
|
+
> ```
|
|
482
|
+
> Here `onItemClick` is the item's own method — no `^` needed.
|
|
483
|
+
> You can still use `^` to reach the parent list component if needed.
|
|
460
484
|
|
|
461
485
|
### Custom item component
|
|
462
486
|
```html
|
|
463
|
-
<div
|
|
487
|
+
<div itemize="items" item-tag="my-item"></div>
|
|
464
488
|
```
|
|
465
|
-
Then define `my-item` as a separate Symbiote component.
|
|
489
|
+
Then define `my-item` as a separate Symbiote component with its own template, methods, and state.
|
|
466
490
|
|
|
467
491
|
### Data formats
|
|
468
492
|
- **Array**: `[{prop: val}, ...]` — items rendered in order
|
|
@@ -799,7 +823,7 @@ import '@symbiotejs/symbiote/core/devMessages.js';
|
|
|
799
823
|
|
|
800
824
|
1. **DON'T** use `this` in template strings — templates are decoupled from component context
|
|
801
825
|
2. **DON'T** nest property keys with dots in state: `'obj.prop'` won't work as a state key
|
|
802
|
-
3. **DON'T** forget `^` prefix when referencing **parent**
|
|
826
|
+
3. **DON'T** forget `^` prefix when referencing **parent** handlers from inline `<template>` itemize items (dumb items without their own class). Custom `item-tag` components with own methods bind directly without `^`
|
|
803
827
|
4. **DON'T** use `@` prefix directly in HTML — it's only for binding syntax (`${{'@attr': 'prop'}}`)
|
|
804
828
|
5. **DON'T** treat `init$` as a regular object — it's processed at connection time
|
|
805
829
|
6. **DON'T** define `template` inside the class body (`static template = html\`...\`` won't work) — it's a static property **setter**, assign it outside: `MyComponent.template = html\`...\``. Same applies to `rootStyles` and `shadowStyles`.
|
package/README.md
CHANGED
|
@@ -9,25 +9,26 @@
|
|
|
9
9
|
|
|
10
10
|
<img src="https://rnd-pro.com/svg/symbiote/index.svg" width="200" alt="Symbiote.js">
|
|
11
11
|
|
|
12
|
-
A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no build step required
|
|
12
|
+
A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional. **~6kb** brotli / **~7kb** gzip.
|
|
13
13
|
|
|
14
|
-
Symbiote.js gives you the convenience of a modern framework while staying close to the native platform
|
|
14
|
+
Symbiote.js gives you the convenience of a modern framework while staying close to the native platform - HTML, CSS, and DOM APIs. Components are real custom elements that work everywhere: in any framework, in plain HTML, or in a micro-frontend architecture. And with **isomorphic mode**, the same component code works on the server and the client - server-rendered pages hydrate automatically, no diffing, no mismatch errors.
|
|
15
15
|
|
|
16
16
|
## What's new in v3
|
|
17
17
|
|
|
18
|
-
- **Server-Side Rendering**
|
|
19
|
-
- **Isomorphic components**
|
|
20
|
-
- **Computed properties**
|
|
21
|
-
- **Path-based router**
|
|
22
|
-
- **Exit animations**
|
|
23
|
-
- **Dev mode**
|
|
24
|
-
- **DSD hydration**
|
|
25
|
-
- **Class property fallback**
|
|
18
|
+
- **Server-Side Rendering** - render components to HTML with `SSR.processHtml()` or stream chunks with `SSR.renderToStream()`. Client-side hydration via `ssrMode` attaches bindings to existing DOM without re-rendering.
|
|
19
|
+
- **Isomorphic components** - `isoMode` flag makes components work in both SSR and client-only scenarios automatically. If server-rendered content exists, it hydrates; otherwise it renders the template from scratch. One component, zero conditional logic.
|
|
20
|
+
- **Computed properties** - reactive derived state with microtask batching.
|
|
21
|
+
- **Path-based router** - optional `AppRouter` module with `:param` extraction, route guards, and lazy loading.
|
|
22
|
+
- **Exit animations** - `animateOut(el)` for CSS-driven exit transitions, integrated into itemize API.
|
|
23
|
+
- **Dev mode** - `Symbiote.devMode` enables verbose warnings; import `devMessages.js` for full human-readable messages.
|
|
24
|
+
- **DSD hydration** - `ssrMode` supports both light DOM and Declarative Shadow DOM.
|
|
25
|
+
- **Class property fallback** - binding keys not in `init$` fall back to own class properties/methods.
|
|
26
|
+
- **Lazy mode** - `lazyMode` flag defers component initialization and rendering based on viewport visibility. Can also be enabled via the `lazy` attribute on `itemize` containers to efficiently handle massive data sets.
|
|
26
27
|
- And [more](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md).
|
|
27
28
|
|
|
28
29
|
## Quick start
|
|
29
30
|
|
|
30
|
-
No install needed
|
|
31
|
+
No install needed - run this directly in a browser:
|
|
31
32
|
|
|
32
33
|
```html
|
|
33
34
|
<script type="module">
|
|
@@ -63,7 +64,7 @@ import Symbiote, { html, css } from '@symbiotejs/symbiote';
|
|
|
63
64
|
|
|
64
65
|
## Isomorphic Web Components
|
|
65
66
|
|
|
66
|
-
One component. Server-rendered or client-rendered
|
|
67
|
+
One component. Server-rendered or client-rendered - automatically. Set `isoMode = true` and the component figures it out: if server-rendered content exists, it hydrates; otherwise it renders from template. No conditional logic, no separate server/client versions:
|
|
67
68
|
```js
|
|
68
69
|
class MyComponent extends Symbiote {
|
|
69
70
|
isoMode = true;
|
|
@@ -80,9 +81,9 @@ MyComponent.template = html`
|
|
|
80
81
|
MyComponent.reg('my-component');
|
|
81
82
|
```
|
|
82
83
|
|
|
83
|
-
This exact code runs **everywhere**
|
|
84
|
+
This exact code runs **everywhere** - SSR on the server, hydration on the client, or pure client rendering. No framework split, no `'use client'` directives, no hydration mismatch errors.
|
|
84
85
|
|
|
85
|
-
### SSR
|
|
86
|
+
### SSR - one class, zero config
|
|
86
87
|
|
|
87
88
|
Server rendering doesn't need a virtual DOM, a reconciler, or framework-specific packages:
|
|
88
89
|
|
|
@@ -103,12 +104,12 @@ For large pages, stream HTML chunks with `SSR.renderToStream()` for faster TTFB.
|
|
|
103
104
|
| | **Symbiote.js** | **Next.js (React)** | **Lit** (`@lit-labs/ssr`) |
|
|
104
105
|
|--|----------------|---------------------|----|
|
|
105
106
|
| **Isomorphic code** | Same code, `isoMode` auto-detects | Server Components vs Client Components split | Same code, but load-order constraints |
|
|
106
|
-
| **Hydration** | Binding-based
|
|
107
|
+
| **Hydration** | Binding-based - attaches to existing DOM, no diffing | `hydrateRoot()` - must produce identical output or errors | Requires `ssr-client` + hydrate support module |
|
|
107
108
|
| **Packages** | 1 module + `linkedom` peer dep | Full framework buy-in | 3 packages: `ssr`, `ssr-client`, `ssr-dom-shim` |
|
|
108
109
|
| **Streaming** | `renderToStream()` async generator | `renderToPipeableStream()` | Iterable `RenderResult` |
|
|
109
|
-
| **Mismatch handling** | Not needed
|
|
110
|
+
| **Mismatch handling** | Not needed - bindings attach to whatever DOM exists | Hard errors if server/client output differs | N/A |
|
|
110
111
|
| **Template output** | Clean HTML with `bind=` attributes | HTML with framework markers | HTML with `<!--lit-part-->` comment markers |
|
|
111
|
-
| **Lock-in** | None
|
|
112
|
+
| **Lock-in** | None - standard Web Components | Full framework commitment | Lit-specific, but Web Components |
|
|
112
113
|
|
|
113
114
|
**Key insight:** There are no hydration mismatches because there's no diffing. The server produces HTML with binding attributes. The client reads those attributes and adds reactivity. That's it.
|
|
114
115
|
|
|
@@ -136,11 +137,11 @@ State changes update the DOM synchronously. No virtual DOM, no scheduling, no su
|
|
|
136
137
|
document.querySelector('my-counter').$.count = 42;
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
This makes it easy to control Symbiote-based widgets and microfrontends from any host application
|
|
140
|
+
This makes it easy to control Symbiote-based widgets and microfrontends from any host application - no framework adapters, just DOM.
|
|
140
141
|
|
|
141
142
|
### Templates
|
|
142
143
|
|
|
143
|
-
Templates are plain HTML strings
|
|
144
|
+
Templates are plain HTML strings - context-free, easy to test, easy to move between files:
|
|
144
145
|
|
|
145
146
|
```js
|
|
146
147
|
// Separate file: my-component.template.js
|
|
@@ -182,11 +183,13 @@ TaskList.template = html`
|
|
|
182
183
|
`;
|
|
183
184
|
```
|
|
184
185
|
|
|
185
|
-
Items have their own state scope. Use the **`^` prefix** to reach higher-level component properties and handlers
|
|
186
|
+
Items have their own state scope. Use the **`^` prefix** to reach higher-level component properties and handlers - `'^onItemClick'` binds to the parent's `onItemClick`, not the item's. Properties referenced via `^` must be defined in the parent's `init$`.
|
|
187
|
+
|
|
188
|
+
> **Performance Tip:** For massive lists, add the `lazy` attribute to the container (`<div itemize="tasks" lazy>`). It defers component initialization until they enter the viewport and cleans them up when they leave, heavily optimizing memory and rendering performance.
|
|
186
189
|
|
|
187
190
|
### Pop-up binding (`^`)
|
|
188
191
|
|
|
189
|
-
The `^` prefix works in any nested component template
|
|
192
|
+
The `^` prefix works in any nested component template - it walks up the DOM tree to find the nearest ancestor that has the property registered in its data context (`init$` or `add$()`):
|
|
190
193
|
|
|
191
194
|
```html
|
|
192
195
|
<!-- Text binding to parent property: -->
|
|
@@ -196,7 +199,7 @@ The `^` prefix works in any nested component template — it walks up the DOM tr
|
|
|
196
199
|
<button ${{onclick: '^parentHandler'}}>Click</button>
|
|
197
200
|
```
|
|
198
201
|
|
|
199
|
-
> **Note:** Class property fallbacks are not checked by the `^` walk
|
|
202
|
+
> **Note:** Class property fallbacks are not checked by the `^` walk - the parent must define the property in `init$`.
|
|
200
203
|
|
|
201
204
|
### Named data contexts
|
|
202
205
|
|
|
@@ -216,7 +219,7 @@ this.$['APP/user'] = 'New name';
|
|
|
216
219
|
|
|
217
220
|
### Shared context (`*`)
|
|
218
221
|
|
|
219
|
-
Inspired by native HTML `name` attributes
|
|
222
|
+
Inspired by native HTML `name` attributes - like how `<input name="group">` groups radio buttons - the `ctx` attribute groups components into a shared data context. Components with the same `ctx` value share `*`-prefixed properties:
|
|
220
223
|
|
|
221
224
|
```html
|
|
222
225
|
<upload-btn ctx="gallery"></upload-btn>
|
|
@@ -242,7 +245,7 @@ class StatusBar extends Symbiote {
|
|
|
242
245
|
}
|
|
243
246
|
```
|
|
244
247
|
|
|
245
|
-
All three components access the same `*files` state
|
|
248
|
+
All three components access the same `*files` state - no parent component, no prop drilling, no global store boilerplate. Just set `ctx="gallery"` in HTML and use `*`-prefixed properties. This makes it trivial to build complex component relationships purely in markup, with ready-made components that don't need to know about each other.
|
|
246
249
|
|
|
247
250
|
The context name can also be inherited via CSS custom property `--ctx`, enabling layout-driven grouping.
|
|
248
251
|
|
|
@@ -276,9 +279,9 @@ task-item {
|
|
|
276
279
|
|
|
277
280
|
### Styling
|
|
278
281
|
|
|
279
|
-
Shadow DOM is **optional** in Symbiote
|
|
282
|
+
Shadow DOM is **optional** in Symbiote - use it when you need isolation, skip it when you don't. This gives full flexibility:
|
|
280
283
|
|
|
281
|
-
**Light DOM**
|
|
284
|
+
**Light DOM** - style components with regular CSS, no barriers:
|
|
282
285
|
|
|
283
286
|
```js
|
|
284
287
|
MyComponent.rootStyles = css`
|
|
@@ -291,7 +294,7 @@ MyComponent.rootStyles = css`
|
|
|
291
294
|
`;
|
|
292
295
|
```
|
|
293
296
|
|
|
294
|
-
**Shadow DOM**
|
|
297
|
+
**Shadow DOM** - opt-in isolation when needed:
|
|
295
298
|
|
|
296
299
|
```js
|
|
297
300
|
class Isolated extends Symbiote {}
|
|
@@ -302,7 +305,7 @@ Isolated.shadowStyles = css`
|
|
|
302
305
|
`;
|
|
303
306
|
```
|
|
304
307
|
|
|
305
|
-
All native CSS features work as expected: CSS variables flow through shadow boundaries, `::part()` exposes internals, modern nesting, `@layer`, `@container`
|
|
308
|
+
All native CSS features work as expected: CSS variables flow through shadow boundaries, `::part()` exposes internals, modern nesting, `@layer`, `@container` - no framework abstractions in the way. Mix light DOM and shadow DOM components freely in the same app.
|
|
306
309
|
|
|
307
310
|
### CSS Data Binding
|
|
308
311
|
|
|
@@ -322,15 +325,15 @@ MyWidget.template = html`
|
|
|
322
325
|
`;
|
|
323
326
|
```
|
|
324
327
|
|
|
325
|
-
CSS values are parsed automatically
|
|
328
|
+
CSS values are parsed automatically - quoted strings become strings, numbers become numbers. Call `this.updateCssData()` to re-read after runtime CSS changes. This enables CSS-driven configuration: theme values, layout parameters, or localized strings - all settable from CSS without touching JS.
|
|
326
329
|
|
|
327
330
|
## Best for
|
|
328
331
|
|
|
329
332
|
- **Complex widgets** embedded in any host application
|
|
330
|
-
- **Micro frontends**
|
|
331
|
-
- **Reusable component libraries**
|
|
332
|
-
- **SSR-powered apps**
|
|
333
|
-
- **Framework-agnostic solutions**
|
|
333
|
+
- **Micro frontends** - standard custom elements, no framework coupling
|
|
334
|
+
- **Reusable component libraries** - works in React, Vue, Angular, or plain HTML
|
|
335
|
+
- **SSR-powered apps** - lightweight server rendering without framework lock-in
|
|
336
|
+
- **Framework-agnostic solutions** - one codebase, any context
|
|
334
337
|
|
|
335
338
|
## Bundle size
|
|
336
339
|
|
|
@@ -350,7 +353,9 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
|
350
353
|
## Docs & Examples
|
|
351
354
|
|
|
352
355
|
- [Documentation](https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md)
|
|
356
|
+
- [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
|
|
353
357
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
358
|
+
- [JSDA-Kit](https://github.com/rnd-pro/jsda-kit) - All-in-one companion tool: server, SSG, bundling, import maps, and native Symbiote.js SSR integration
|
|
354
359
|
- [AI Reference](https://github.com/symbiotejs/symbiote.js/blob/main/AI_REFERENCE.md)
|
|
355
360
|
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
356
361
|
|
|
@@ -358,4 +363,4 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
|
358
363
|
|
|
359
364
|
---
|
|
360
365
|
|
|
361
|
-
© [rnd-pro.com](https://rnd-pro.com)
|
|
366
|
+
© [rnd-pro.com](https://rnd-pro.com) - MIT License
|
package/core/Symbiote.js
CHANGED
|
@@ -19,6 +19,9 @@ const trustedHTML = globalThis.trustedTypes ? trustedTypes.createPolicy('symbiot
|
|
|
19
19
|
|
|
20
20
|
/** @template S */
|
|
21
21
|
export class Symbiote extends HTMLElement {
|
|
22
|
+
/** @type {IntersectionObserver} */
|
|
23
|
+
static lazyObserver;
|
|
24
|
+
|
|
22
25
|
/** @type {Boolean} */
|
|
23
26
|
#initialized;
|
|
24
27
|
/** @type {String} */
|
|
@@ -187,6 +190,8 @@ export class Symbiote extends HTMLElement {
|
|
|
187
190
|
this.isVirtual = false;
|
|
188
191
|
/** @type {Boolean} */
|
|
189
192
|
this.allowTemplateInits = true;
|
|
193
|
+
/** @type {Boolean} */
|
|
194
|
+
this.lazyMode = false;
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
/** @returns {String} */
|
|
@@ -516,7 +521,52 @@ export class Symbiote extends HTMLElement {
|
|
|
516
521
|
}
|
|
517
522
|
|
|
518
523
|
connectedCallback() {
|
|
519
|
-
this
|
|
524
|
+
if (this.lazyMode) {
|
|
525
|
+
if (!Symbiote.lazyObserver) {
|
|
526
|
+
Symbiote.lazyObserver = new IntersectionObserver((entries) => {
|
|
527
|
+
entries.forEach((ent) => {
|
|
528
|
+
/** @type {any} */
|
|
529
|
+
let tgt = ent.target;
|
|
530
|
+
if (ent.isIntersecting) {
|
|
531
|
+
tgt.style.minHeight = '';
|
|
532
|
+
tgt.style.minWidth = '';
|
|
533
|
+
tgt.#initComponent();
|
|
534
|
+
} else {
|
|
535
|
+
tgt.#lazyDestroy();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
Symbiote.lazyObserver.observe(this);
|
|
541
|
+
} else {
|
|
542
|
+
this.#initComponent();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
#lazyDestroy() {
|
|
547
|
+
if (!this.connectedOnce) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
let rect = this.getBoundingClientRect();
|
|
551
|
+
if (rect.height) {
|
|
552
|
+
this.style.minHeight = rect.height + 'px';
|
|
553
|
+
this.style.minWidth = rect.width + 'px';
|
|
554
|
+
}
|
|
555
|
+
if (this.shadowRoot) {
|
|
556
|
+
this.shadowRoot.innerHTML = '';
|
|
557
|
+
} else {
|
|
558
|
+
this.innerHTML = '';
|
|
559
|
+
}
|
|
560
|
+
for (let sub of this.allSubs) {
|
|
561
|
+
sub.remove();
|
|
562
|
+
this.allSubs.delete(sub);
|
|
563
|
+
}
|
|
564
|
+
this.#localCtx = null;
|
|
565
|
+
this.templateProcessors.clear();
|
|
566
|
+
|
|
567
|
+
this.connectedOnce = false;
|
|
568
|
+
this.#dataCtxInitialized = false;
|
|
569
|
+
this.#initialized = false;
|
|
520
570
|
}
|
|
521
571
|
|
|
522
572
|
destroyCallback() {}
|
|
@@ -532,6 +582,9 @@ export class Symbiote extends HTMLElement {
|
|
|
532
582
|
destructionDelay = 100;
|
|
533
583
|
disconnectedCallback() {
|
|
534
584
|
if (globalThis.__SYMBIOTE_SSR) return;
|
|
585
|
+
if (this.lazyMode && Symbiote.lazyObserver) {
|
|
586
|
+
Symbiote.lazyObserver.unobserve(this);
|
|
587
|
+
}
|
|
535
588
|
// if element wasn't connected, there is no need to disconnect it
|
|
536
589
|
if (!this.connectedOnce) {
|
|
537
590
|
return;
|
|
@@ -30,7 +30,7 @@ import { setupItemize } from './itemizeSetup.js';
|
|
|
30
30
|
* @param {T} fnCtx
|
|
31
31
|
*/
|
|
32
32
|
export function itemizeProcessor(fr, fnCtx) {
|
|
33
|
-
setupItemize(fr, fnCtx, ({ el, itemClass, repeatDataKey, clientSSR }) => {
|
|
33
|
+
setupItemize(fr, fnCtx, ({ el, itemClass, repeatDataKey, clientSSR, isLazy }) => {
|
|
34
34
|
/** @type {*[]} */
|
|
35
35
|
let prevData = [];
|
|
36
36
|
|
|
@@ -88,6 +88,9 @@ export function itemizeProcessor(fr, fnCtx) {
|
|
|
88
88
|
let fragment = document.createDocumentFragment();
|
|
89
89
|
for (let i = prevLen; i < newLen; i++) {
|
|
90
90
|
let repeatItem = new itemClass();
|
|
91
|
+
if (isLazy) {
|
|
92
|
+
repeatItem.lazyMode = true;
|
|
93
|
+
}
|
|
91
94
|
Object.assign((repeatItem?.init$ || repeatItem), items[i]);
|
|
92
95
|
fragment.appendChild(repeatItem);
|
|
93
96
|
}
|
|
@@ -151,6 +154,9 @@ export function itemizeProcessor(fr, fnCtx) {
|
|
|
151
154
|
newChildren.push(existing);
|
|
152
155
|
} else {
|
|
153
156
|
let repeatItem = new itemClass();
|
|
157
|
+
if (isLazy) {
|
|
158
|
+
repeatItem.lazyMode = true;
|
|
159
|
+
}
|
|
154
160
|
Object.assign((repeatItem?.init$ || repeatItem), item);
|
|
155
161
|
newChildren.push(repeatItem);
|
|
156
162
|
}
|
|
@@ -193,6 +199,9 @@ export function itemizeProcessor(fr, fnCtx) {
|
|
|
193
199
|
fragment = document.createDocumentFragment();
|
|
194
200
|
}
|
|
195
201
|
let repeatItem = new itemClass();
|
|
202
|
+
if (isLazy) {
|
|
203
|
+
repeatItem.lazyMode = true;
|
|
204
|
+
}
|
|
196
205
|
Object.assign((repeatItem?.init$ || repeatItem), items[i]);
|
|
197
206
|
fragment.appendChild(repeatItem);
|
|
198
207
|
}
|
package/core/itemizeProcessor.js
CHANGED
|
@@ -8,7 +8,7 @@ import { setupItemize } from './itemizeSetup.js';
|
|
|
8
8
|
* @param {T} fnCtx
|
|
9
9
|
*/
|
|
10
10
|
export function itemizeProcessor(fr, fnCtx) {
|
|
11
|
-
setupItemize(fr, fnCtx, ({ el, itemClass, repeatDataKey, clientSSR }) => {
|
|
11
|
+
setupItemize(fr, fnCtx, ({ el, itemClass, repeatDataKey, clientSSR, isLazy }) => {
|
|
12
12
|
fnCtx.sub(repeatDataKey, (data) => {
|
|
13
13
|
if (!data) {
|
|
14
14
|
while (el.firstChild) {
|
|
@@ -34,6 +34,9 @@ export function itemizeProcessor(fr, fnCtx) {
|
|
|
34
34
|
fragment = document.createDocumentFragment();
|
|
35
35
|
}
|
|
36
36
|
let repeatItem = new itemClass();
|
|
37
|
+
if (isLazy) {
|
|
38
|
+
repeatItem.lazyMode = true;
|
|
39
|
+
}
|
|
37
40
|
Object.assign((repeatItem?.init$ || repeatItem), item);
|
|
38
41
|
fragment.appendChild(repeatItem);
|
|
39
42
|
}
|
package/core/itemizeSetup.js
CHANGED
|
@@ -8,6 +8,7 @@ import { initPropFallback } from './initPropFallback.js';
|
|
|
8
8
|
* @property {*} itemClass
|
|
9
9
|
* @property {string} repeatDataKey
|
|
10
10
|
* @property {boolean} clientSSR
|
|
11
|
+
* @property {boolean} isLazy
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -78,7 +79,8 @@ export function setupItemize(fr, fnCtx, handler) {
|
|
|
78
79
|
initPropFallback(fnCtx, repeatDataKey);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
let isLazy = el.hasAttribute('lazy');
|
|
83
|
+
handler({ el, itemClass, repeatDataKey, clientSSR, isLazy });
|
|
82
84
|
|
|
83
85
|
if (!globalThis.__SYMBIOTE_SSR) {
|
|
84
86
|
el.removeAttribute(DICT.LIST_ATTR);
|
package/core/warn.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @type {{ devMode: boolean }} */
|
|
1
|
+
/** @type {{ devMode: boolean, hintShown: boolean }} */
|
|
2
2
|
export let devState = {
|
|
3
3
|
get devMode() {
|
|
4
4
|
return !!globalThis.__SYMBIOTE_DEV_MODE;
|
|
@@ -6,11 +6,26 @@ export let devState = {
|
|
|
6
6
|
set devMode(val) {
|
|
7
7
|
globalThis.__SYMBIOTE_DEV_MODE = val;
|
|
8
8
|
},
|
|
9
|
+
hintShown: false,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** @type {Record<number, string>} */
|
|
13
|
+
let CRITICAL = {
|
|
14
|
+
8: 'Tag already registered with different class',
|
|
15
|
+
9: 'CSS data parse error',
|
|
16
|
+
16: 'Itemize data must be Array or Object',
|
|
9
17
|
};
|
|
10
18
|
|
|
11
19
|
/** @param {string} type @param {number} code */
|
|
12
20
|
function _log(type, code) {
|
|
13
|
-
|
|
21
|
+
let msg = CRITICAL[code];
|
|
22
|
+
if (!msg) return;
|
|
23
|
+
let prefix = type === 'error' ? 'E' : 'W';
|
|
24
|
+
console[type](`[Symbiote ${prefix}${code}] ${msg}`);
|
|
25
|
+
if (!devState.hintShown) {
|
|
26
|
+
devState.hintShown = true;
|
|
27
|
+
console[type]('Import \'@symbiotejs/symbiote/core/devMessages.js\' for detailed messages.');
|
|
28
|
+
}
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@symbiotejs/symbiote",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.0",
|
|
5
5
|
"description": "Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components",
|
|
6
6
|
"author": "team@rnd-pro.com",
|
|
7
7
|
"license": "MIT",
|
package/types/core/Symbiote.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { html } from "./html.js";
|
|
2
2
|
export { css } from "./css.js";
|
|
3
3
|
export class Symbiote<S> extends HTMLElement {
|
|
4
|
+
static lazyObserver: IntersectionObserver;
|
|
4
5
|
static __tpl: HTMLTemplateElement;
|
|
5
6
|
static set devMode(val: boolean);
|
|
6
7
|
static get devMode(): boolean;
|
|
@@ -38,6 +39,7 @@ export class Symbiote<S> extends HTMLElement {
|
|
|
38
39
|
allowCustomTemplate: boolean;
|
|
39
40
|
isVirtual: boolean;
|
|
40
41
|
allowTemplateInits: boolean;
|
|
42
|
+
lazyMode: boolean;
|
|
41
43
|
get cssCtxName(): string;
|
|
42
44
|
get ctxName(): string;
|
|
43
45
|
get localCtx(): PubSub<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Symbiote.d.ts","sourceRoot":"","sources":["../../core/Symbiote.js"],"names":[],"mappings":";;AAoBA,sBADc,CAAC;
|
|
1
|
+
{"version":3,"file":"Symbiote.d.ts","sourceRoot":"","sources":["../../core/Symbiote.js"],"names":[],"mappings":";;AAoBA,sBADc,CAAC;IAGb,qBADW,oBAAoB,CACX;IAuBpB,cADW,mBAAmB,CACjB;IAEb,iCAEC;IAED,8BAEC;IAkBD,wBAAgB;IAgKhB,+BAJwB,CAAC,SAAZ,aAAU,uBAEZ,CAAC;;;MA0CX;IAoTD,qCAA+B;IAwC/B,iDAFa,OAAO,QAAQ,CAkB3B;IAED,wBAKC;IAGD;;aAYC;IAmHD,6BADY,SAAS,aAAa,QAOjC;IAGD,+BADY,SAAS,aAAa,QAOjC;IAGD,8BADY,SAAS,aAAa,EAIjC;IAGD,gCADY,SAAS,aAAa,EAIjC;IA5nBD,cA+BC;IA3ID,gCAEC;IAED,qBAAiB;IACjB,uBAAmB;IAiBnB,kBAHW,SAAS,gBAAgB,0BAuFnC;IAxDG,aAAiD;IA6DnD,OADW,CAAC,CACoB;IAEhC;;MAAmC;IAEnC,oBADW,GAAG,CAAC,CAAC,EAAE,EAAE,gBAAgB,gBAAW,EAAE,KAAK,eAAU,KAAK,IAAI,CAAC,CACvC;IAEnC;;MAA8B;IAC9B,kBAAwB;IAExB,qBAAwB;IAExB,sBAAyB;IAEzB,wBAA0B;IAE1B,0BAA6B;IAI7B,iBAAoB;IAEpB,6BAAgC;IAEhC,mBAAsB;IAEtB,4BAA8B;IAE9B,kBAAqB;IAIvB,yBAEC;IAGD,sBASC;IAGD,4BAKC;IAGD,6BAEC;IAuDD,IALuB,CAAC,SAAX,MAAO,CAAE,QACX,CAAC,WACD,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,wBAuC/B;IAGD,2BAIC;IAGD,uBAIC;IAQD,IALuB,CAAC,SAAX,MAAO,CAAE,qBAEX,CAAC,CAAC,CAAC,CAAC,2BAOd;IAMD,UAHW,OAAO,CAAC,CAAC,CAAC,2BAOpB;IAGD,SADc,CAAC,CA6Bd;IAMD,YAHW,OAAO,CAAC,CAAC,CAAC,mCAcpB;IAED,8BAgBC;IAdG,4CASE;IAmFF,0BAAwC;IAwB1C,uBAAyB;IAG3B,0BAqBC;IA4BD,wBAAoB;IAUpB,yBAAuB;IACvB,6BA8BC;IA+CD,oEAeC;IAMD,yDAoBC;IAYD,0BAME;IAMF,0CAFW,GAAG,QAgBb;IAED,yBAGC;IAOD,8EAmBC;;CA+BF;;mBAhyBkB,aAAa;qBAEX,iBAAiB;2BACX,iBAAiB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"itemizeProcessor-keyed.d.ts","sourceRoot":"","sources":["../../core/itemizeProcessor-keyed.js"],"names":[],"mappings":"AA+BA,iCAJgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC,
|
|
1
|
+
{"version":3,"file":"itemizeProcessor-keyed.d.ts","sourceRoot":"","sources":["../../core/itemizeProcessor-keyed.js"],"names":[],"mappings":"AA+BA,iCAJgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC,QA2LX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"itemizeProcessor.d.ts","sourceRoot":"","sources":["../../core/itemizeProcessor.js"],"names":[],"mappings":"AASA,iCAJgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC,
|
|
1
|
+
{"version":3,"file":"itemizeProcessor.d.ts","sourceRoot":"","sources":["../../core/itemizeProcessor.js"],"names":[],"mappings":"AASA,iCAJgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC,QA4DX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"itemizeSetup.d.ts","sourceRoot":"","sources":["../../core/itemizeSetup.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"itemizeSetup.d.ts","sourceRoot":"","sources":["../../core/itemizeSetup.js"],"names":[],"mappings":"AAuBA,6BALgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC,WACD,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,QAoE3C;;QAnFa,OAAO;eACP,GAAC;mBACD,MAAM;eACN,OAAO;YACP,OAAO"}
|
package/types/core/warn.d.ts
CHANGED
package/types/core/warn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warn.d.ts","sourceRoot":"","sources":["../../core/warn.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"warn.d.ts","sourceRoot":"","sources":["../../core/warn.js"],"names":[],"mappings":"AAkCA,8BAHW,MAAM,WACF,GAAG,EAAA,QAIjB;AAMD,6BAHW,MAAM,WACF,GAAG,EAAA,QAIjB;AA3CD,qBADW;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CASjD"}
|