dalila 1.9.5 → 1.9.7
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 +20 -3
- package/dist/cli/check.js +6 -1
- package/dist/runtime/bind.d.ts +23 -0
- package/dist/runtime/bind.js +762 -41
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ bind(document.getElementById('app')!, ctx);
|
|
|
58
58
|
|
|
59
59
|
### Runtime
|
|
60
60
|
|
|
61
|
-
- [Template Binding](./docs/runtime/bind.md) — `bind()`, `mount()`, `configure()`, text interpolation, events
|
|
61
|
+
- [Template Binding](./docs/runtime/bind.md) — `bind()`, `mount()`, `configure()`, transitions, portal, text interpolation, events
|
|
62
62
|
- [Components](./docs/runtime/component.md) — `defineComponent`, typed props/emits/refs, slots
|
|
63
63
|
- [FOUC Prevention](./docs/runtime/fouc-prevention.md) — Automatic token hiding
|
|
64
64
|
|
|
@@ -76,7 +76,7 @@ bind(document.getElementById('app')!, ctx);
|
|
|
76
76
|
- [when](./docs/core/when.md) — Conditional visibility
|
|
77
77
|
- [match](./docs/core/match.md) — Switch-style rendering
|
|
78
78
|
- [for](./docs/core/for.md) — List rendering with keyed diffing
|
|
79
|
-
- [Virtual Lists](./docs/core/virtual.md) — Fixed-height windowed rendering
|
|
79
|
+
- [Virtual Lists](./docs/core/virtual.md) — Fixed and dynamic-height windowed rendering with infinite-scroll hooks
|
|
80
80
|
|
|
81
81
|
### Data
|
|
82
82
|
|
|
@@ -108,7 +108,7 @@ Firefox extension workflows:
|
|
|
108
108
|
|
|
109
109
|
```
|
|
110
110
|
dalila → signal, computed, effect, batch, ...
|
|
111
|
-
dalila/runtime → bind(), mount(), configure(), defineComponent()
|
|
111
|
+
dalila/runtime → bind(), mount(), configure(), createPortalTarget(), defineComponent()
|
|
112
112
|
dalila/context → createContext, provide, inject
|
|
113
113
|
dalila/http → createHttpClient with XSRF protection
|
|
114
114
|
```
|
|
@@ -143,6 +143,23 @@ const dispose = mount('.app', { count: signal(0) });
|
|
|
143
143
|
dispose();
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
### Transitions and Portal
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<div d-when="open" d-transition="fade">Panel</div>
|
|
150
|
+
<div d-portal="showModal ? '#modal-root' : null">Modal content</div>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { configure, createPortalTarget } from 'dalila/runtime';
|
|
155
|
+
|
|
156
|
+
const modalTarget = createPortalTarget('modal-root');
|
|
157
|
+
|
|
158
|
+
configure({
|
|
159
|
+
transitions: [{ name: 'fade', duration: 250 }],
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
146
163
|
### Scopes
|
|
147
164
|
|
|
148
165
|
```ts
|
package/dist/cli/check.js
CHANGED
|
@@ -520,7 +520,7 @@ function extractTemplateIdentifiers(html) {
|
|
|
520
520
|
i++;
|
|
521
521
|
}
|
|
522
522
|
// --- 2. Directive scanning (supports single and double quotes) ---
|
|
523
|
-
const DIRECTIVE_RE = /\b(d-each|d-virtual-each|d-virtual-height|d-virtual-item-height|d-virtual-overscan|d-if|d-when|d-match|d-html|d-attr-[a-zA-Z][\w-]*|d-bind-[a-zA-Z][\w-]*|d-on-[a-zA-Z][\w-]*|d-form-error|d-form|d-array)\s*=\s*(['"])([\s\S]*?)\2/g;
|
|
523
|
+
const DIRECTIVE_RE = /\b(d-each|d-virtual-each|d-virtual-height|d-virtual-item-height|d-virtual-estimated-height|d-virtual-measure|d-virtual-infinite|d-virtual-overscan|d-if|d-when|d-match|d-portal|d-html|d-attr-[a-zA-Z][\w-]*|d-bind-[a-zA-Z][\w-]*|d-on-[a-zA-Z][\w-]*|d-form-error|d-form|d-array)\s*=\s*(['"])([\s\S]*?)\2/g;
|
|
524
524
|
DIRECTIVE_RE.lastIndex = 0;
|
|
525
525
|
let match;
|
|
526
526
|
while ((match = DIRECTIVE_RE.exec(html))) {
|
|
@@ -528,6 +528,8 @@ function extractTemplateIdentifiers(html) {
|
|
|
528
528
|
const value = match[3].trim();
|
|
529
529
|
if (!value)
|
|
530
530
|
continue;
|
|
531
|
+
if (directive === 'd-virtual-measure' && value.toLowerCase() === 'auto')
|
|
532
|
+
continue;
|
|
531
533
|
const roots = extractRootIdentifiers(value);
|
|
532
534
|
const loc = offsetToLineCol(match.index);
|
|
533
535
|
for (const name of roots) {
|
|
@@ -691,6 +693,9 @@ const LOOP_FORCED_CHECK_SOURCES = new Set([
|
|
|
691
693
|
'd-virtual-each',
|
|
692
694
|
'd-virtual-height',
|
|
693
695
|
'd-virtual-item-height',
|
|
696
|
+
'd-virtual-estimated-height',
|
|
697
|
+
'd-virtual-measure',
|
|
698
|
+
'd-virtual-infinite',
|
|
694
699
|
'd-virtual-overscan',
|
|
695
700
|
]);
|
|
696
701
|
function checkHtmlContent(html, filePath, validIdentifiers, diagnostics) {
|
package/dist/runtime/bind.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module dalila/runtime
|
|
8
8
|
*/
|
|
9
|
+
import { Signal } from '../core/index.js';
|
|
9
10
|
import type { Component } from './component.js';
|
|
10
11
|
export interface BindOptions {
|
|
11
12
|
/**
|
|
@@ -30,6 +31,10 @@ export interface BindOptions {
|
|
|
30
31
|
components?: Record<string, Component> | Component[];
|
|
31
32
|
/** Error policy for component `ctx.onMount()` callbacks. Default: 'log'. */
|
|
32
33
|
onMountError?: 'log' | 'throw';
|
|
34
|
+
/**
|
|
35
|
+
* Optional runtime transition registry used by `d-transition`.
|
|
36
|
+
*/
|
|
37
|
+
transitions?: TransitionConfig[];
|
|
33
38
|
/**
|
|
34
39
|
* Internal flag — set by fromHtml for router/template rendering.
|
|
35
40
|
* Skips HMR context registration but KEEPS d-ready/d-loading lifecycle.
|
|
@@ -59,6 +64,24 @@ export interface BindHandle {
|
|
|
59
64
|
getRef(name: string): Element | null;
|
|
60
65
|
getRefs(): Readonly<Record<string, Element>>;
|
|
61
66
|
}
|
|
67
|
+
export interface TransitionConfig {
|
|
68
|
+
name: string;
|
|
69
|
+
enter?: (el: HTMLElement) => void;
|
|
70
|
+
leave?: (el: HTMLElement) => void;
|
|
71
|
+
duration?: number;
|
|
72
|
+
}
|
|
73
|
+
export type VirtualListAlign = 'start' | 'center' | 'end';
|
|
74
|
+
export interface VirtualScrollToIndexOptions {
|
|
75
|
+
align?: VirtualListAlign;
|
|
76
|
+
behavior?: ScrollBehavior;
|
|
77
|
+
}
|
|
78
|
+
export interface VirtualListController {
|
|
79
|
+
scrollToIndex: (index: number, options?: VirtualScrollToIndexOptions) => void;
|
|
80
|
+
refresh: () => void;
|
|
81
|
+
}
|
|
82
|
+
export declare function getVirtualListController(target: Element | null): VirtualListController | null;
|
|
83
|
+
export declare function scrollToVirtualIndex(target: Element | null, index: number, options?: VirtualScrollToIndexOptions): boolean;
|
|
84
|
+
export declare function createPortalTarget(id: string): Signal<Element | null>;
|
|
62
85
|
/**
|
|
63
86
|
* Set global defaults for all `bind()` / `mount()` calls.
|
|
64
87
|
*
|
package/dist/runtime/bind.js
CHANGED
|
@@ -97,6 +97,7 @@ function warn(message) {
|
|
|
97
97
|
console.warn(`[Dalila] ${message}`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
const portalSyncByElement = new WeakMap();
|
|
100
101
|
function describeBindRoot(root) {
|
|
101
102
|
const explicit = root.getAttribute('data-component') ||
|
|
102
103
|
root.getAttribute('data-devtools-label') ||
|
|
@@ -1135,13 +1136,231 @@ function bindEmit(root, ctx, cleanups) {
|
|
|
1135
1136
|
}
|
|
1136
1137
|
}
|
|
1137
1138
|
}
|
|
1139
|
+
function createTransitionRegistry(transitions) {
|
|
1140
|
+
const registry = new Map();
|
|
1141
|
+
if (!transitions)
|
|
1142
|
+
return registry;
|
|
1143
|
+
for (const cfg of transitions) {
|
|
1144
|
+
if (!cfg || typeof cfg !== 'object')
|
|
1145
|
+
continue;
|
|
1146
|
+
const name = typeof cfg.name === 'string' ? cfg.name.trim() : '';
|
|
1147
|
+
if (!name) {
|
|
1148
|
+
warn('configure({ transitions }): each transition must have a non-empty "name"');
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
registry.set(name, cfg);
|
|
1152
|
+
}
|
|
1153
|
+
return registry;
|
|
1154
|
+
}
|
|
1155
|
+
function readTransitionNames(el) {
|
|
1156
|
+
const raw = el.getAttribute('d-transition');
|
|
1157
|
+
if (!raw)
|
|
1158
|
+
return [];
|
|
1159
|
+
return raw
|
|
1160
|
+
.split(/\s+/)
|
|
1161
|
+
.map(v => v.trim())
|
|
1162
|
+
.filter(Boolean);
|
|
1163
|
+
}
|
|
1164
|
+
function parseCssTimeToMs(value) {
|
|
1165
|
+
const token = value.trim();
|
|
1166
|
+
if (!token)
|
|
1167
|
+
return 0;
|
|
1168
|
+
if (token.endsWith('ms')) {
|
|
1169
|
+
const ms = Number(token.slice(0, -2));
|
|
1170
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
1171
|
+
}
|
|
1172
|
+
if (token.endsWith('s')) {
|
|
1173
|
+
const seconds = Number(token.slice(0, -1));
|
|
1174
|
+
return Number.isFinite(seconds) ? Math.max(0, seconds * 1000) : 0;
|
|
1175
|
+
}
|
|
1176
|
+
const fallback = Number(token);
|
|
1177
|
+
return Number.isFinite(fallback) ? Math.max(0, fallback) : 0;
|
|
1178
|
+
}
|
|
1179
|
+
function getTransitionDurationMs(el, names, registry) {
|
|
1180
|
+
let durationFromRegistry = 0;
|
|
1181
|
+
for (const name of names) {
|
|
1182
|
+
const cfg = registry.get(name);
|
|
1183
|
+
if (!cfg)
|
|
1184
|
+
continue;
|
|
1185
|
+
if (typeof cfg.duration === 'number' && Number.isFinite(cfg.duration)) {
|
|
1186
|
+
durationFromRegistry = Math.max(durationFromRegistry, Math.max(0, cfg.duration));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
let durationFromCss = 0;
|
|
1190
|
+
if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
|
|
1191
|
+
const style = window.getComputedStyle(el);
|
|
1192
|
+
const durations = style.transitionDuration.split(',');
|
|
1193
|
+
const delays = style.transitionDelay.split(',');
|
|
1194
|
+
const total = Math.max(durations.length, delays.length);
|
|
1195
|
+
for (let i = 0; i < total; i++) {
|
|
1196
|
+
const duration = parseCssTimeToMs(durations[Math.min(i, durations.length - 1)] ?? '0ms');
|
|
1197
|
+
const delay = parseCssTimeToMs(delays[Math.min(i, delays.length - 1)] ?? '0ms');
|
|
1198
|
+
durationFromCss = Math.max(durationFromCss, duration + delay);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return Math.max(durationFromRegistry, durationFromCss);
|
|
1202
|
+
}
|
|
1203
|
+
function runTransitionHook(phase, el, names, registry) {
|
|
1204
|
+
for (const name of names) {
|
|
1205
|
+
const cfg = registry.get(name);
|
|
1206
|
+
const hook = phase === 'enter' ? cfg?.enter : cfg?.leave;
|
|
1207
|
+
if (typeof hook !== 'function')
|
|
1208
|
+
continue;
|
|
1209
|
+
try {
|
|
1210
|
+
hook(el);
|
|
1211
|
+
}
|
|
1212
|
+
catch (err) {
|
|
1213
|
+
warn(`d-transition (${name}): ${phase} hook failed (${err.message || String(err)})`);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
function syncPortalElement(el) {
|
|
1218
|
+
const sync = portalSyncByElement.get(el);
|
|
1219
|
+
sync?.();
|
|
1220
|
+
}
|
|
1221
|
+
function createTransitionController(el, registry, cleanups) {
|
|
1222
|
+
const names = readTransitionNames(el);
|
|
1223
|
+
const hasTransition = names.length > 0;
|
|
1224
|
+
let token = 0;
|
|
1225
|
+
let timeoutId = null;
|
|
1226
|
+
const cancelPending = () => {
|
|
1227
|
+
token++;
|
|
1228
|
+
if (timeoutId != null) {
|
|
1229
|
+
clearTimeout(timeoutId);
|
|
1230
|
+
timeoutId = null;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
cleanups.push(cancelPending);
|
|
1234
|
+
const enter = () => {
|
|
1235
|
+
cancelPending();
|
|
1236
|
+
if (!hasTransition)
|
|
1237
|
+
return;
|
|
1238
|
+
el.removeAttribute('data-leave');
|
|
1239
|
+
el.setAttribute('data-enter', '');
|
|
1240
|
+
runTransitionHook('enter', el, names, registry);
|
|
1241
|
+
};
|
|
1242
|
+
const leave = (onDone) => {
|
|
1243
|
+
cancelPending();
|
|
1244
|
+
if (!hasTransition) {
|
|
1245
|
+
onDone();
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const current = ++token;
|
|
1249
|
+
el.removeAttribute('data-enter');
|
|
1250
|
+
el.setAttribute('data-leave', '');
|
|
1251
|
+
runTransitionHook('leave', el, names, registry);
|
|
1252
|
+
const durationMs = getTransitionDurationMs(el, names, registry);
|
|
1253
|
+
if (durationMs <= 0) {
|
|
1254
|
+
if (current === token)
|
|
1255
|
+
onDone();
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
timeoutId = setTimeout(() => {
|
|
1259
|
+
timeoutId = null;
|
|
1260
|
+
if (current !== token)
|
|
1261
|
+
return;
|
|
1262
|
+
onDone();
|
|
1263
|
+
}, durationMs);
|
|
1264
|
+
};
|
|
1265
|
+
return { hasTransition, enter, leave };
|
|
1266
|
+
}
|
|
1267
|
+
function bindPortal(root, ctx, cleanups) {
|
|
1268
|
+
const elements = qsaIncludingRoot(root, '[d-portal]');
|
|
1269
|
+
for (const el of elements) {
|
|
1270
|
+
const rawExpression = el.getAttribute('d-portal')?.trim();
|
|
1271
|
+
if (!rawExpression)
|
|
1272
|
+
continue;
|
|
1273
|
+
let expressionAst = null;
|
|
1274
|
+
let fallbackSelector = null;
|
|
1275
|
+
try {
|
|
1276
|
+
expressionAst = parseExpression(rawExpression);
|
|
1277
|
+
}
|
|
1278
|
+
catch {
|
|
1279
|
+
// Allow selector shorthand: d-portal="#modal-root"
|
|
1280
|
+
fallbackSelector = rawExpression;
|
|
1281
|
+
}
|
|
1282
|
+
const htmlEl = el;
|
|
1283
|
+
const anchor = document.createComment('d-portal');
|
|
1284
|
+
htmlEl.parentNode?.insertBefore(anchor, htmlEl);
|
|
1285
|
+
const coerceTarget = (value) => {
|
|
1286
|
+
const resolved = resolve(value);
|
|
1287
|
+
if (resolved == null || resolved === false)
|
|
1288
|
+
return null;
|
|
1289
|
+
if (typeof resolved === 'string') {
|
|
1290
|
+
const selector = resolved.trim();
|
|
1291
|
+
if (!selector)
|
|
1292
|
+
return null;
|
|
1293
|
+
if (typeof document === 'undefined')
|
|
1294
|
+
return null;
|
|
1295
|
+
const target = document.querySelector(selector);
|
|
1296
|
+
if (!target) {
|
|
1297
|
+
warn(`d-portal: target "${selector}" not found`);
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
return target;
|
|
1301
|
+
}
|
|
1302
|
+
if (typeof Element !== 'undefined' && resolved instanceof Element) {
|
|
1303
|
+
return resolved;
|
|
1304
|
+
}
|
|
1305
|
+
warn('d-portal: expression must resolve to selector string, Element, or null');
|
|
1306
|
+
return null;
|
|
1307
|
+
};
|
|
1308
|
+
const restoreToAnchor = () => {
|
|
1309
|
+
const hostParent = anchor.parentNode;
|
|
1310
|
+
if (!hostParent)
|
|
1311
|
+
return;
|
|
1312
|
+
if (htmlEl.parentNode === hostParent)
|
|
1313
|
+
return;
|
|
1314
|
+
const next = anchor.nextSibling;
|
|
1315
|
+
if (next)
|
|
1316
|
+
hostParent.insertBefore(htmlEl, next);
|
|
1317
|
+
else
|
|
1318
|
+
hostParent.appendChild(htmlEl);
|
|
1319
|
+
};
|
|
1320
|
+
const syncPortal = () => {
|
|
1321
|
+
let target = null;
|
|
1322
|
+
if (expressionAst) {
|
|
1323
|
+
const result = evalExpressionAst(expressionAst, ctx);
|
|
1324
|
+
if (!result.ok) {
|
|
1325
|
+
if (result.reason === 'missing_identifier') {
|
|
1326
|
+
warn(`d-portal: ${result.message}`);
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
warn(`d-portal: invalid expression "${rawExpression}"`);
|
|
1330
|
+
}
|
|
1331
|
+
target = null;
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
target = coerceTarget(result.value);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
target = coerceTarget(fallbackSelector);
|
|
1339
|
+
}
|
|
1340
|
+
if (!target) {
|
|
1341
|
+
restoreToAnchor();
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (htmlEl.parentNode !== target) {
|
|
1345
|
+
target.appendChild(htmlEl);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
portalSyncByElement.set(htmlEl, syncPortal);
|
|
1349
|
+
bindEffect(htmlEl, syncPortal);
|
|
1350
|
+
cleanups.push(() => {
|
|
1351
|
+
portalSyncByElement.delete(htmlEl);
|
|
1352
|
+
restoreToAnchor();
|
|
1353
|
+
anchor.remove();
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1138
1357
|
// ============================================================================
|
|
1139
1358
|
// d-when Directive
|
|
1140
1359
|
// ============================================================================
|
|
1141
1360
|
/**
|
|
1142
1361
|
* Bind all [d-when] directives within root
|
|
1143
1362
|
*/
|
|
1144
|
-
function bindWhen(root, ctx, cleanups) {
|
|
1363
|
+
function bindWhen(root, ctx, cleanups, transitionRegistry) {
|
|
1145
1364
|
const elements = qsaIncludingRoot(root, '[when], [d-when]');
|
|
1146
1365
|
for (const el of elements) {
|
|
1147
1366
|
const attrName = el.hasAttribute('when') ? 'when' : 'd-when';
|
|
@@ -1154,13 +1373,32 @@ function bindWhen(root, ctx, cleanups) {
|
|
|
1154
1373
|
continue;
|
|
1155
1374
|
}
|
|
1156
1375
|
const htmlEl = el;
|
|
1376
|
+
const transitions = createTransitionController(htmlEl, transitionRegistry, cleanups);
|
|
1157
1377
|
// Apply initial state synchronously to avoid FOUC (flash of unstyled content)
|
|
1158
1378
|
const initialValue = !!resolve(binding);
|
|
1159
|
-
|
|
1379
|
+
if (initialValue) {
|
|
1380
|
+
htmlEl.style.display = '';
|
|
1381
|
+
if (transitions.hasTransition) {
|
|
1382
|
+
htmlEl.removeAttribute('data-leave');
|
|
1383
|
+
htmlEl.setAttribute('data-enter', '');
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
htmlEl.style.display = 'none';
|
|
1388
|
+
htmlEl.removeAttribute('data-enter');
|
|
1389
|
+
htmlEl.removeAttribute('data-leave');
|
|
1390
|
+
}
|
|
1160
1391
|
// Then create reactive effect to keep it updated
|
|
1161
1392
|
bindEffect(htmlEl, () => {
|
|
1162
1393
|
const value = !!resolve(binding);
|
|
1163
|
-
|
|
1394
|
+
if (value) {
|
|
1395
|
+
htmlEl.style.display = '';
|
|
1396
|
+
transitions.enter();
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
transitions.leave(() => {
|
|
1400
|
+
htmlEl.style.display = 'none';
|
|
1401
|
+
});
|
|
1164
1402
|
});
|
|
1165
1403
|
}
|
|
1166
1404
|
}
|
|
@@ -1260,6 +1498,47 @@ function readVirtualHeightOption(raw, ctx) {
|
|
|
1260
1498
|
}
|
|
1261
1499
|
return trimmed;
|
|
1262
1500
|
}
|
|
1501
|
+
function readVirtualMeasureOption(raw, ctx) {
|
|
1502
|
+
if (!raw)
|
|
1503
|
+
return false;
|
|
1504
|
+
const trimmed = raw.trim();
|
|
1505
|
+
if (!trimmed)
|
|
1506
|
+
return false;
|
|
1507
|
+
if (trimmed.toLowerCase() === 'auto')
|
|
1508
|
+
return true;
|
|
1509
|
+
const fromCtx = ctx[trimmed];
|
|
1510
|
+
if (fromCtx === undefined)
|
|
1511
|
+
return false;
|
|
1512
|
+
const resolved = resolve(fromCtx);
|
|
1513
|
+
if (resolved === true)
|
|
1514
|
+
return true;
|
|
1515
|
+
if (typeof resolved === 'string' && resolved.trim().toLowerCase() === 'auto')
|
|
1516
|
+
return true;
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
function readVirtualCallbackOption(raw, ctx, label) {
|
|
1520
|
+
if (!raw)
|
|
1521
|
+
return null;
|
|
1522
|
+
const trimmed = raw.trim();
|
|
1523
|
+
if (!trimmed)
|
|
1524
|
+
return null;
|
|
1525
|
+
const fromCtx = ctx[trimmed];
|
|
1526
|
+
if (fromCtx === undefined) {
|
|
1527
|
+
warn(`${label}: "${trimmed}" not found in context`);
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
if (typeof fromCtx === 'function' && !isSignal(fromCtx)) {
|
|
1531
|
+
return fromCtx;
|
|
1532
|
+
}
|
|
1533
|
+
if (isSignal(fromCtx)) {
|
|
1534
|
+
const resolved = fromCtx();
|
|
1535
|
+
if (typeof resolved === 'function') {
|
|
1536
|
+
return resolved;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
warn(`${label}: "${trimmed}" must resolve to a function`);
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1263
1542
|
function createVirtualSpacer(template, kind) {
|
|
1264
1543
|
const spacer = template.cloneNode(false);
|
|
1265
1544
|
spacer.removeAttribute('id');
|
|
@@ -1281,13 +1560,199 @@ function createVirtualSpacer(template, kind) {
|
|
|
1281
1560
|
spacer.style.listStyle = 'none';
|
|
1282
1561
|
return spacer;
|
|
1283
1562
|
}
|
|
1563
|
+
const virtualScrollRestoreCache = new Map();
|
|
1564
|
+
const VIRTUAL_SCROLL_RESTORE_CACHE_MAX_ENTRIES = 256;
|
|
1565
|
+
function getVirtualScrollRestoreValue(key) {
|
|
1566
|
+
const value = virtualScrollRestoreCache.get(key);
|
|
1567
|
+
if (value === undefined)
|
|
1568
|
+
return undefined;
|
|
1569
|
+
// Touch entry to keep LRU ordering.
|
|
1570
|
+
virtualScrollRestoreCache.delete(key);
|
|
1571
|
+
virtualScrollRestoreCache.set(key, value);
|
|
1572
|
+
return value;
|
|
1573
|
+
}
|
|
1574
|
+
function setVirtualScrollRestoreValue(key, value) {
|
|
1575
|
+
virtualScrollRestoreCache.delete(key);
|
|
1576
|
+
virtualScrollRestoreCache.set(key, value);
|
|
1577
|
+
while (virtualScrollRestoreCache.size > VIRTUAL_SCROLL_RESTORE_CACHE_MAX_ENTRIES) {
|
|
1578
|
+
const oldestKey = virtualScrollRestoreCache.keys().next().value;
|
|
1579
|
+
if (!oldestKey)
|
|
1580
|
+
break;
|
|
1581
|
+
virtualScrollRestoreCache.delete(oldestKey);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function clampVirtual(value, min, max) {
|
|
1585
|
+
return Math.max(min, Math.min(max, value));
|
|
1586
|
+
}
|
|
1587
|
+
function getElementPositionPath(el) {
|
|
1588
|
+
const parts = [];
|
|
1589
|
+
let current = el;
|
|
1590
|
+
while (current) {
|
|
1591
|
+
const tag = current.tagName.toLowerCase();
|
|
1592
|
+
const parentEl = current.parentElement;
|
|
1593
|
+
if (!parentEl) {
|
|
1594
|
+
parts.push(tag);
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
let index = 1;
|
|
1598
|
+
let sib = current.previousElementSibling;
|
|
1599
|
+
while (sib) {
|
|
1600
|
+
index++;
|
|
1601
|
+
sib = sib.previousElementSibling;
|
|
1602
|
+
}
|
|
1603
|
+
parts.push(`${tag}:${index}`);
|
|
1604
|
+
current = parentEl;
|
|
1605
|
+
}
|
|
1606
|
+
return parts.reverse().join('>');
|
|
1607
|
+
}
|
|
1608
|
+
const virtualRestoreDocumentIds = new WeakMap();
|
|
1609
|
+
let nextVirtualRestoreDocumentId = 0;
|
|
1610
|
+
function getVirtualRestoreDocumentId(doc) {
|
|
1611
|
+
const existing = virtualRestoreDocumentIds.get(doc);
|
|
1612
|
+
if (existing !== undefined)
|
|
1613
|
+
return existing;
|
|
1614
|
+
const next = ++nextVirtualRestoreDocumentId;
|
|
1615
|
+
virtualRestoreDocumentIds.set(doc, next);
|
|
1616
|
+
return next;
|
|
1617
|
+
}
|
|
1618
|
+
function getVirtualRestoreKey(doc, templatePath, scrollContainer, bindingName, keyBinding) {
|
|
1619
|
+
const locationPath = typeof window !== 'undefined'
|
|
1620
|
+
? `${window.location.pathname}${window.location.search}`
|
|
1621
|
+
: '';
|
|
1622
|
+
const containerIdentity = scrollContainer?.id
|
|
1623
|
+
? `#${scrollContainer.id}`
|
|
1624
|
+
: (scrollContainer ? getElementPositionPath(scrollContainer) : '');
|
|
1625
|
+
const docId = getVirtualRestoreDocumentId(doc);
|
|
1626
|
+
return `${docId}|${locationPath}|${bindingName}|${keyBinding ?? ''}|${containerIdentity}|${templatePath}`;
|
|
1627
|
+
}
|
|
1628
|
+
class VirtualHeightsIndex {
|
|
1629
|
+
constructor(itemCount, estimatedHeight) {
|
|
1630
|
+
this.itemCount = 0;
|
|
1631
|
+
this.estimatedHeight = 1;
|
|
1632
|
+
this.tree = [0];
|
|
1633
|
+
this.overrides = new Map();
|
|
1634
|
+
this.reset(itemCount, estimatedHeight);
|
|
1635
|
+
}
|
|
1636
|
+
get count() {
|
|
1637
|
+
return this.itemCount;
|
|
1638
|
+
}
|
|
1639
|
+
snapshotOverrides() {
|
|
1640
|
+
return new Map(this.overrides);
|
|
1641
|
+
}
|
|
1642
|
+
reset(itemCount, estimatedHeight, seed) {
|
|
1643
|
+
this.itemCount = Number.isFinite(itemCount) ? Math.max(0, Math.floor(itemCount)) : 0;
|
|
1644
|
+
this.estimatedHeight = Number.isFinite(estimatedHeight) ? Math.max(1, estimatedHeight) : 1;
|
|
1645
|
+
this.tree = new Array(this.itemCount + 1).fill(0);
|
|
1646
|
+
this.overrides.clear();
|
|
1647
|
+
for (let i = 0; i < this.itemCount; i++) {
|
|
1648
|
+
this.addAt(i + 1, this.estimatedHeight);
|
|
1649
|
+
}
|
|
1650
|
+
if (!seed)
|
|
1651
|
+
return;
|
|
1652
|
+
for (const [index, height] of seed.entries()) {
|
|
1653
|
+
if (index < 0 || index >= this.itemCount)
|
|
1654
|
+
continue;
|
|
1655
|
+
this.set(index, height);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
set(index, height) {
|
|
1659
|
+
if (!Number.isFinite(height) || height <= 0)
|
|
1660
|
+
return false;
|
|
1661
|
+
if (index < 0 || index >= this.itemCount)
|
|
1662
|
+
return false;
|
|
1663
|
+
const next = Math.max(1, height);
|
|
1664
|
+
const current = this.get(index);
|
|
1665
|
+
if (Math.abs(next - current) < 0.5)
|
|
1666
|
+
return false;
|
|
1667
|
+
this.addAt(index + 1, next - current);
|
|
1668
|
+
if (Math.abs(next - this.estimatedHeight) < 0.5) {
|
|
1669
|
+
this.overrides.delete(index);
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
this.overrides.set(index, next);
|
|
1673
|
+
}
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
get(index) {
|
|
1677
|
+
if (index < 0 || index >= this.itemCount)
|
|
1678
|
+
return this.estimatedHeight;
|
|
1679
|
+
return this.overrides.get(index) ?? this.estimatedHeight;
|
|
1680
|
+
}
|
|
1681
|
+
prefix(endExclusive) {
|
|
1682
|
+
if (endExclusive <= 0)
|
|
1683
|
+
return 0;
|
|
1684
|
+
const clampedEnd = Math.min(this.itemCount, Math.max(0, Math.floor(endExclusive)));
|
|
1685
|
+
let i = clampedEnd;
|
|
1686
|
+
let sum = 0;
|
|
1687
|
+
while (i > 0) {
|
|
1688
|
+
sum += this.tree[i];
|
|
1689
|
+
i -= i & -i;
|
|
1690
|
+
}
|
|
1691
|
+
return sum;
|
|
1692
|
+
}
|
|
1693
|
+
total() {
|
|
1694
|
+
return this.prefix(this.itemCount);
|
|
1695
|
+
}
|
|
1696
|
+
lowerBound(target) {
|
|
1697
|
+
if (this.itemCount === 0 || target <= 0)
|
|
1698
|
+
return 0;
|
|
1699
|
+
let idx = 0;
|
|
1700
|
+
let bit = 1;
|
|
1701
|
+
while ((bit << 1) <= this.itemCount)
|
|
1702
|
+
bit <<= 1;
|
|
1703
|
+
let sum = 0;
|
|
1704
|
+
while (bit > 0) {
|
|
1705
|
+
const next = idx + bit;
|
|
1706
|
+
if (next <= this.itemCount && sum + this.tree[next] < target) {
|
|
1707
|
+
idx = next;
|
|
1708
|
+
sum += this.tree[next];
|
|
1709
|
+
}
|
|
1710
|
+
bit >>= 1;
|
|
1711
|
+
}
|
|
1712
|
+
return Math.min(this.itemCount, idx);
|
|
1713
|
+
}
|
|
1714
|
+
indexAtOffset(offset) {
|
|
1715
|
+
if (this.itemCount === 0)
|
|
1716
|
+
return 0;
|
|
1717
|
+
if (!Number.isFinite(offset) || offset <= 0)
|
|
1718
|
+
return 0;
|
|
1719
|
+
const totalHeight = this.total();
|
|
1720
|
+
if (offset >= totalHeight)
|
|
1721
|
+
return this.itemCount - 1;
|
|
1722
|
+
const idx = this.lowerBound(offset + 0.0001);
|
|
1723
|
+
return clampVirtual(idx, 0, this.itemCount - 1);
|
|
1724
|
+
}
|
|
1725
|
+
addAt(treeIndex, delta) {
|
|
1726
|
+
let i = treeIndex;
|
|
1727
|
+
while (i <= this.itemCount) {
|
|
1728
|
+
this.tree[i] += delta;
|
|
1729
|
+
i += i & -i;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
function readVirtualListApi(target) {
|
|
1734
|
+
if (!target)
|
|
1735
|
+
return null;
|
|
1736
|
+
return target.__dalilaVirtualList ?? null;
|
|
1737
|
+
}
|
|
1738
|
+
export function getVirtualListController(target) {
|
|
1739
|
+
return readVirtualListApi(target);
|
|
1740
|
+
}
|
|
1741
|
+
export function scrollToVirtualIndex(target, index, options) {
|
|
1742
|
+
const controller = readVirtualListApi(target);
|
|
1743
|
+
if (!controller)
|
|
1744
|
+
return false;
|
|
1745
|
+
controller.scrollToIndex(index, options);
|
|
1746
|
+
return true;
|
|
1747
|
+
}
|
|
1284
1748
|
/**
|
|
1285
1749
|
* Bind all [d-virtual-each] directives within root.
|
|
1286
1750
|
*
|
|
1287
|
-
*
|
|
1288
|
-
* - Fixed item height (
|
|
1289
|
-
* -
|
|
1290
|
-
* -
|
|
1751
|
+
* Supports:
|
|
1752
|
+
* - Fixed item height (`d-virtual-item-height`)
|
|
1753
|
+
* - Dynamic item height (`d-virtual-measure="auto"`)
|
|
1754
|
+
* - Infinite scroll callback (`d-virtual-infinite`)
|
|
1755
|
+
* - Parent element as vertical scroll container
|
|
1291
1756
|
*/
|
|
1292
1757
|
function bindVirtualEach(root, ctx, cleanups) {
|
|
1293
1758
|
const elements = qsaIncludingRoot(root, '[d-virtual-each]')
|
|
@@ -1299,16 +1764,28 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1299
1764
|
const itemHeightBinding = normalizeBinding(el.getAttribute('d-virtual-item-height'));
|
|
1300
1765
|
const itemHeightRaw = itemHeightBinding ?? el.getAttribute('d-virtual-item-height');
|
|
1301
1766
|
const itemHeightValue = readVirtualNumberOption(itemHeightRaw, ctx, 'd-virtual-item-height');
|
|
1302
|
-
const
|
|
1303
|
-
|
|
1767
|
+
const fixedItemHeight = Number.isFinite(itemHeightValue) && itemHeightValue > 0
|
|
1768
|
+
? itemHeightValue
|
|
1769
|
+
: NaN;
|
|
1770
|
+
const dynamicHeight = readVirtualMeasureOption(normalizeBinding(el.getAttribute('d-virtual-measure')) ?? el.getAttribute('d-virtual-measure'), ctx);
|
|
1771
|
+
if (!dynamicHeight && (!Number.isFinite(fixedItemHeight) || fixedItemHeight <= 0)) {
|
|
1304
1772
|
warn(`d-virtual-each: invalid item height on "${bindingName}". Falling back to d-each.`);
|
|
1305
1773
|
el.setAttribute('d-each', bindingName);
|
|
1306
1774
|
el.removeAttribute('d-virtual-each');
|
|
1307
1775
|
el.removeAttribute('d-virtual-item-height');
|
|
1776
|
+
el.removeAttribute('d-virtual-estimated-height');
|
|
1777
|
+
el.removeAttribute('d-virtual-measure');
|
|
1778
|
+
el.removeAttribute('d-virtual-infinite');
|
|
1308
1779
|
el.removeAttribute('d-virtual-overscan');
|
|
1309
1780
|
el.removeAttribute('d-virtual-height');
|
|
1310
1781
|
continue;
|
|
1311
1782
|
}
|
|
1783
|
+
const estimatedHeightBinding = normalizeBinding(el.getAttribute('d-virtual-estimated-height'));
|
|
1784
|
+
const estimatedHeightRaw = estimatedHeightBinding ?? el.getAttribute('d-virtual-estimated-height');
|
|
1785
|
+
const estimatedHeightValue = readVirtualNumberOption(estimatedHeightRaw, ctx, 'd-virtual-estimated-height');
|
|
1786
|
+
const estimatedItemHeight = Number.isFinite(estimatedHeightValue) && estimatedHeightValue > 0
|
|
1787
|
+
? estimatedHeightValue
|
|
1788
|
+
: (Number.isFinite(fixedItemHeight) ? fixedItemHeight : 48);
|
|
1312
1789
|
const overscanBinding = normalizeBinding(el.getAttribute('d-virtual-overscan'));
|
|
1313
1790
|
const overscanRaw = overscanBinding ?? el.getAttribute('d-virtual-overscan');
|
|
1314
1791
|
const overscanValue = readVirtualNumberOption(overscanRaw, ctx, 'd-virtual-overscan');
|
|
@@ -1316,15 +1793,20 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1316
1793
|
? Math.max(0, Math.floor(overscanValue))
|
|
1317
1794
|
: 6;
|
|
1318
1795
|
const viewportHeight = readVirtualHeightOption(normalizeBinding(el.getAttribute('d-virtual-height')) ?? el.getAttribute('d-virtual-height'), ctx);
|
|
1796
|
+
const onEndReached = readVirtualCallbackOption(normalizeBinding(el.getAttribute('d-virtual-infinite')) ?? el.getAttribute('d-virtual-infinite'), ctx, 'd-virtual-infinite');
|
|
1319
1797
|
let binding = ctx[bindingName];
|
|
1320
1798
|
if (binding === undefined) {
|
|
1321
1799
|
warn(`d-virtual-each: "${bindingName}" not found in context`);
|
|
1322
1800
|
binding = [];
|
|
1323
1801
|
}
|
|
1802
|
+
const templatePathBeforeDetach = getElementPositionPath(el);
|
|
1324
1803
|
const comment = document.createComment('d-virtual-each');
|
|
1325
1804
|
el.parentNode?.replaceChild(comment, el);
|
|
1326
1805
|
el.removeAttribute('d-virtual-each');
|
|
1327
1806
|
el.removeAttribute('d-virtual-item-height');
|
|
1807
|
+
el.removeAttribute('d-virtual-estimated-height');
|
|
1808
|
+
el.removeAttribute('d-virtual-measure');
|
|
1809
|
+
el.removeAttribute('d-virtual-infinite');
|
|
1328
1810
|
el.removeAttribute('d-virtual-overscan');
|
|
1329
1811
|
el.removeAttribute('d-virtual-height');
|
|
1330
1812
|
const keyBinding = normalizeBinding(el.getAttribute('d-key'));
|
|
@@ -1341,8 +1823,14 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1341
1823
|
if (!scrollContainer.style.overflowY)
|
|
1342
1824
|
scrollContainer.style.overflowY = 'auto';
|
|
1343
1825
|
}
|
|
1826
|
+
const restoreKey = getVirtualRestoreKey(el.ownerDocument, templatePathBeforeDetach, scrollContainer, bindingName, keyBinding);
|
|
1827
|
+
const savedScrollTop = getVirtualScrollRestoreValue(restoreKey);
|
|
1828
|
+
if (scrollContainer && Number.isFinite(savedScrollTop)) {
|
|
1829
|
+
scrollContainer.scrollTop = Math.max(0, savedScrollTop);
|
|
1830
|
+
}
|
|
1344
1831
|
const clonesByKey = new Map();
|
|
1345
1832
|
const disposesByKey = new Map();
|
|
1833
|
+
const observedElements = new Set();
|
|
1346
1834
|
const metadataByKey = new Map();
|
|
1347
1835
|
const itemsByKey = new Map();
|
|
1348
1836
|
const objectKeyIds = new WeakMap();
|
|
@@ -1352,6 +1840,7 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1352
1840
|
const missingKeyWarned = new Set();
|
|
1353
1841
|
let warnedNonArray = false;
|
|
1354
1842
|
let warnedViewportFallback = false;
|
|
1843
|
+
let heightsIndex = dynamicHeight ? new VirtualHeightsIndex(0, estimatedItemHeight) : null;
|
|
1355
1844
|
const getObjectKeyId = (value) => {
|
|
1356
1845
|
const existing = objectKeyIds.get(value);
|
|
1357
1846
|
if (existing !== undefined)
|
|
@@ -1406,6 +1895,7 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1406
1895
|
}
|
|
1407
1896
|
return index;
|
|
1408
1897
|
};
|
|
1898
|
+
let rowResizeObserver = null;
|
|
1409
1899
|
function createClone(key, item, index, count) {
|
|
1410
1900
|
const clone = template.cloneNode(true);
|
|
1411
1901
|
const itemCtx = Object.create(ctx);
|
|
@@ -1431,6 +1921,7 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1431
1921
|
itemCtx.$odd = metadata.$odd;
|
|
1432
1922
|
itemCtx.$even = metadata.$even;
|
|
1433
1923
|
clone.setAttribute('data-dalila-internal-bound', '');
|
|
1924
|
+
clone.setAttribute('data-dalila-virtual-index', String(index));
|
|
1434
1925
|
const dispose = bind(clone, itemCtx, { _skipLifecycle: true });
|
|
1435
1926
|
disposesByKey.set(key, dispose);
|
|
1436
1927
|
clonesByKey.set(key, clone);
|
|
@@ -1446,9 +1937,17 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1446
1937
|
metadata.$odd.set(index % 2 !== 0);
|
|
1447
1938
|
metadata.$even.set(index % 2 === 0);
|
|
1448
1939
|
}
|
|
1940
|
+
const clone = clonesByKey.get(key);
|
|
1941
|
+
if (clone) {
|
|
1942
|
+
clone.setAttribute('data-dalila-virtual-index', String(index));
|
|
1943
|
+
}
|
|
1449
1944
|
}
|
|
1450
1945
|
function removeKey(key) {
|
|
1451
1946
|
const clone = clonesByKey.get(key);
|
|
1947
|
+
if (clone && rowResizeObserver && observedElements.has(clone)) {
|
|
1948
|
+
rowResizeObserver.unobserve(clone);
|
|
1949
|
+
observedElements.delete(clone);
|
|
1950
|
+
}
|
|
1452
1951
|
clone?.remove();
|
|
1453
1952
|
clonesByKey.delete(key);
|
|
1454
1953
|
metadataByKey.delete(key);
|
|
@@ -1460,31 +1959,106 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1460
1959
|
}
|
|
1461
1960
|
}
|
|
1462
1961
|
let currentItems = [];
|
|
1962
|
+
let lastEndReachedCount = -1;
|
|
1963
|
+
let endReachedPending = false;
|
|
1964
|
+
const remapDynamicHeights = (prevItems, nextItems) => {
|
|
1965
|
+
if (!dynamicHeight || !heightsIndex)
|
|
1966
|
+
return;
|
|
1967
|
+
const heightsByKey = new Map();
|
|
1968
|
+
for (let i = 0; i < prevItems.length; i++) {
|
|
1969
|
+
const key = keyValueToString(readKeyValue(prevItems[i], i), i);
|
|
1970
|
+
if (!heightsByKey.has(key)) {
|
|
1971
|
+
heightsByKey.set(key, heightsIndex.get(i));
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
heightsIndex.reset(nextItems.length, estimatedItemHeight);
|
|
1975
|
+
for (let i = 0; i < nextItems.length; i++) {
|
|
1976
|
+
const key = keyValueToString(readKeyValue(nextItems[i], i), i);
|
|
1977
|
+
const height = heightsByKey.get(key);
|
|
1978
|
+
if (height !== undefined) {
|
|
1979
|
+
heightsIndex.set(i, height);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
};
|
|
1983
|
+
const replaceItems = (nextItems) => {
|
|
1984
|
+
remapDynamicHeights(currentItems, nextItems);
|
|
1985
|
+
currentItems = nextItems;
|
|
1986
|
+
};
|
|
1987
|
+
const maybeTriggerEndReached = (visibleEnd, totalCount) => {
|
|
1988
|
+
if (!onEndReached || totalCount === 0)
|
|
1989
|
+
return;
|
|
1990
|
+
if (visibleEnd < totalCount)
|
|
1991
|
+
return;
|
|
1992
|
+
if (lastEndReachedCount === totalCount || endReachedPending)
|
|
1993
|
+
return;
|
|
1994
|
+
lastEndReachedCount = totalCount;
|
|
1995
|
+
const result = onEndReached();
|
|
1996
|
+
if (result && typeof result.then === 'function') {
|
|
1997
|
+
endReachedPending = true;
|
|
1998
|
+
Promise.resolve(result)
|
|
1999
|
+
.catch(() => { })
|
|
2000
|
+
.finally(() => {
|
|
2001
|
+
endReachedPending = false;
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
1463
2005
|
function renderVirtualList(items) {
|
|
1464
2006
|
const parent = comment.parentNode;
|
|
1465
2007
|
if (!parent)
|
|
1466
2008
|
return;
|
|
2009
|
+
if (dynamicHeight && heightsIndex && heightsIndex.count !== items.length) {
|
|
2010
|
+
heightsIndex.reset(items.length, estimatedItemHeight);
|
|
2011
|
+
}
|
|
1467
2012
|
const viewportHeightValue = scrollContainer?.clientHeight ?? 0;
|
|
1468
|
-
const effectiveViewportHeight = viewportHeightValue > 0
|
|
2013
|
+
const effectiveViewportHeight = viewportHeightValue > 0
|
|
2014
|
+
? viewportHeightValue
|
|
2015
|
+
: (dynamicHeight ? estimatedItemHeight * 10 : fixedItemHeight * 10);
|
|
1469
2016
|
const scrollTop = scrollContainer?.scrollTop ?? 0;
|
|
1470
2017
|
if (viewportHeightValue <= 0 && !warnedViewportFallback) {
|
|
1471
2018
|
warnedViewportFallback = true;
|
|
1472
2019
|
warn('d-virtual-each: scroll container has no measurable height. Using fallback viewport size.');
|
|
1473
2020
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
2021
|
+
let start = 0;
|
|
2022
|
+
let end = 0;
|
|
2023
|
+
let topOffset = 0;
|
|
2024
|
+
let bottomOffset = 0;
|
|
2025
|
+
let totalHeight = 0;
|
|
2026
|
+
let visibleEndForEndReached = 0;
|
|
2027
|
+
if (dynamicHeight && heightsIndex) {
|
|
2028
|
+
totalHeight = heightsIndex.total();
|
|
2029
|
+
if (items.length > 0) {
|
|
2030
|
+
const visibleStart = heightsIndex.indexAtOffset(scrollTop);
|
|
2031
|
+
const visibleEnd = clampVirtual(heightsIndex.lowerBound(scrollTop + effectiveViewportHeight) + 1, visibleStart + 1, items.length);
|
|
2032
|
+
visibleEndForEndReached = visibleEnd;
|
|
2033
|
+
start = clampVirtual(visibleStart - overscan, 0, items.length);
|
|
2034
|
+
end = clampVirtual(visibleEnd + overscan, start, items.length);
|
|
2035
|
+
topOffset = heightsIndex.prefix(start);
|
|
2036
|
+
bottomOffset = Math.max(0, totalHeight - heightsIndex.prefix(end));
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
else {
|
|
2040
|
+
const range = computeVirtualRange({
|
|
2041
|
+
itemCount: items.length,
|
|
2042
|
+
itemHeight: fixedItemHeight,
|
|
2043
|
+
scrollTop,
|
|
2044
|
+
viewportHeight: effectiveViewportHeight,
|
|
2045
|
+
overscan,
|
|
2046
|
+
});
|
|
2047
|
+
start = range.start;
|
|
2048
|
+
end = range.end;
|
|
2049
|
+
topOffset = range.topOffset;
|
|
2050
|
+
bottomOffset = range.bottomOffset;
|
|
2051
|
+
totalHeight = range.totalHeight;
|
|
2052
|
+
visibleEndForEndReached = clampVirtual(Math.ceil((scrollTop + effectiveViewportHeight) / fixedItemHeight), 0, items.length);
|
|
2053
|
+
}
|
|
2054
|
+
topSpacer.style.height = `${topOffset}px`;
|
|
2055
|
+
bottomSpacer.style.height = `${bottomOffset}px`;
|
|
2056
|
+
topSpacer.setAttribute('data-dalila-virtual-total', String(totalHeight));
|
|
1483
2057
|
const orderedClones = [];
|
|
1484
2058
|
const orderedKeys = [];
|
|
1485
2059
|
const nextKeys = new Set();
|
|
1486
2060
|
const changedKeys = new Set();
|
|
1487
|
-
for (let i =
|
|
2061
|
+
for (let i = start; i < end; i++) {
|
|
1488
2062
|
const item = items[i];
|
|
1489
2063
|
let key = keyValueToString(readKeyValue(item, i), i);
|
|
1490
2064
|
if (nextKeys.has(key)) {
|
|
@@ -1510,7 +2084,7 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1510
2084
|
if (!changedKeys.has(key))
|
|
1511
2085
|
continue;
|
|
1512
2086
|
removeKey(key);
|
|
1513
|
-
orderedClones[i] = createClone(key, items[
|
|
2087
|
+
orderedClones[i] = createClone(key, items[start + i], start + i, items.length);
|
|
1514
2088
|
}
|
|
1515
2089
|
for (const key of Array.from(clonesByKey.keys())) {
|
|
1516
2090
|
if (nextKeys.has(key))
|
|
@@ -1525,6 +2099,22 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1525
2099
|
}
|
|
1526
2100
|
referenceNode = clone;
|
|
1527
2101
|
}
|
|
2102
|
+
if (dynamicHeight && rowResizeObserver) {
|
|
2103
|
+
const nextObserved = new Set(orderedClones);
|
|
2104
|
+
for (const clone of Array.from(observedElements)) {
|
|
2105
|
+
if (nextObserved.has(clone))
|
|
2106
|
+
continue;
|
|
2107
|
+
rowResizeObserver.unobserve(clone);
|
|
2108
|
+
observedElements.delete(clone);
|
|
2109
|
+
}
|
|
2110
|
+
for (const clone of orderedClones) {
|
|
2111
|
+
if (observedElements.has(clone))
|
|
2112
|
+
continue;
|
|
2113
|
+
rowResizeObserver.observe(clone);
|
|
2114
|
+
observedElements.add(clone);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
maybeTriggerEndReached(visibleEndForEndReached, items.length);
|
|
1528
2118
|
}
|
|
1529
2119
|
let framePending = false;
|
|
1530
2120
|
let pendingRaf = null;
|
|
@@ -1552,28 +2142,89 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1552
2142
|
const onScroll = () => scheduleRender();
|
|
1553
2143
|
const onResize = () => scheduleRender();
|
|
1554
2144
|
scrollContainer?.addEventListener('scroll', onScroll, { passive: true });
|
|
1555
|
-
|
|
2145
|
+
let containerResizeObserver = null;
|
|
2146
|
+
if (typeof ResizeObserver !== 'undefined' && scrollContainer) {
|
|
2147
|
+
containerResizeObserver = new ResizeObserver(() => scheduleRender());
|
|
2148
|
+
containerResizeObserver.observe(scrollContainer);
|
|
2149
|
+
}
|
|
2150
|
+
else if (typeof window !== 'undefined') {
|
|
1556
2151
|
window.addEventListener('resize', onResize);
|
|
1557
2152
|
}
|
|
2153
|
+
if (dynamicHeight && typeof ResizeObserver !== 'undefined' && heightsIndex) {
|
|
2154
|
+
rowResizeObserver = new ResizeObserver((entries) => {
|
|
2155
|
+
let changed = false;
|
|
2156
|
+
for (const entry of entries) {
|
|
2157
|
+
const target = entry.target;
|
|
2158
|
+
const indexRaw = target.getAttribute('data-dalila-virtual-index');
|
|
2159
|
+
if (!indexRaw)
|
|
2160
|
+
continue;
|
|
2161
|
+
const index = Number(indexRaw);
|
|
2162
|
+
if (!Number.isFinite(index))
|
|
2163
|
+
continue;
|
|
2164
|
+
const measured = entry.contentRect?.height;
|
|
2165
|
+
if (!Number.isFinite(measured) || measured <= 0)
|
|
2166
|
+
continue;
|
|
2167
|
+
changed = heightsIndex.set(index, measured) || changed;
|
|
2168
|
+
}
|
|
2169
|
+
if (changed)
|
|
2170
|
+
scheduleRender();
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
const scrollToIndex = (index, options) => {
|
|
2174
|
+
if (!scrollContainer || currentItems.length === 0)
|
|
2175
|
+
return;
|
|
2176
|
+
const safeIndex = clampVirtual(Math.floor(index), 0, currentItems.length - 1);
|
|
2177
|
+
const viewportSize = scrollContainer.clientHeight > 0
|
|
2178
|
+
? scrollContainer.clientHeight
|
|
2179
|
+
: (dynamicHeight ? estimatedItemHeight * 10 : fixedItemHeight * 10);
|
|
2180
|
+
const align = options?.align ?? 'start';
|
|
2181
|
+
let top = dynamicHeight && heightsIndex
|
|
2182
|
+
? heightsIndex.prefix(safeIndex)
|
|
2183
|
+
: safeIndex * fixedItemHeight;
|
|
2184
|
+
const itemSize = dynamicHeight && heightsIndex
|
|
2185
|
+
? heightsIndex.get(safeIndex)
|
|
2186
|
+
: fixedItemHeight;
|
|
2187
|
+
if (align === 'center') {
|
|
2188
|
+
top = top - (viewportSize / 2) + (itemSize / 2);
|
|
2189
|
+
}
|
|
2190
|
+
else if (align === 'end') {
|
|
2191
|
+
top = top - viewportSize + itemSize;
|
|
2192
|
+
}
|
|
2193
|
+
top = Math.max(0, top);
|
|
2194
|
+
if (options?.behavior && typeof scrollContainer.scrollTo === 'function') {
|
|
2195
|
+
scrollContainer.scrollTo({ top, behavior: options.behavior });
|
|
2196
|
+
}
|
|
2197
|
+
else {
|
|
2198
|
+
scrollContainer.scrollTop = top;
|
|
2199
|
+
}
|
|
2200
|
+
scheduleRender();
|
|
2201
|
+
};
|
|
2202
|
+
const virtualApi = {
|
|
2203
|
+
scrollToIndex,
|
|
2204
|
+
refresh: scheduleRender,
|
|
2205
|
+
};
|
|
2206
|
+
if (scrollContainer) {
|
|
2207
|
+
scrollContainer.__dalilaVirtualList = virtualApi;
|
|
2208
|
+
}
|
|
1558
2209
|
if (isSignal(binding)) {
|
|
1559
2210
|
bindEffect(scrollContainer ?? el, () => {
|
|
1560
2211
|
const value = binding();
|
|
1561
2212
|
if (Array.isArray(value)) {
|
|
1562
2213
|
warnedNonArray = false;
|
|
1563
|
-
|
|
2214
|
+
replaceItems(value);
|
|
1564
2215
|
}
|
|
1565
2216
|
else {
|
|
1566
2217
|
if (!warnedNonArray) {
|
|
1567
2218
|
warnedNonArray = true;
|
|
1568
2219
|
warn(`d-virtual-each: "${bindingName}" is not an array or signal-of-array`);
|
|
1569
2220
|
}
|
|
1570
|
-
|
|
2221
|
+
replaceItems([]);
|
|
1571
2222
|
}
|
|
1572
2223
|
renderVirtualList(currentItems);
|
|
1573
2224
|
});
|
|
1574
2225
|
}
|
|
1575
2226
|
else if (Array.isArray(binding)) {
|
|
1576
|
-
|
|
2227
|
+
replaceItems(binding);
|
|
1577
2228
|
renderVirtualList(currentItems);
|
|
1578
2229
|
}
|
|
1579
2230
|
else {
|
|
@@ -1581,7 +2232,10 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1581
2232
|
}
|
|
1582
2233
|
cleanups.push(() => {
|
|
1583
2234
|
scrollContainer?.removeEventListener('scroll', onScroll);
|
|
1584
|
-
if (
|
|
2235
|
+
if (containerResizeObserver) {
|
|
2236
|
+
containerResizeObserver.disconnect();
|
|
2237
|
+
}
|
|
2238
|
+
else if (typeof window !== 'undefined') {
|
|
1585
2239
|
window.removeEventListener('resize', onResize);
|
|
1586
2240
|
}
|
|
1587
2241
|
if (pendingRaf != null && typeof cancelAnimationFrame === 'function') {
|
|
@@ -1591,6 +2245,17 @@ function bindVirtualEach(root, ctx, cleanups) {
|
|
|
1591
2245
|
if (pendingTimeout != null)
|
|
1592
2246
|
clearTimeout(pendingTimeout);
|
|
1593
2247
|
pendingTimeout = null;
|
|
2248
|
+
if (rowResizeObserver) {
|
|
2249
|
+
rowResizeObserver.disconnect();
|
|
2250
|
+
}
|
|
2251
|
+
observedElements.clear();
|
|
2252
|
+
if (scrollContainer) {
|
|
2253
|
+
setVirtualScrollRestoreValue(restoreKey, scrollContainer.scrollTop);
|
|
2254
|
+
const host = scrollContainer;
|
|
2255
|
+
if (host.__dalilaVirtualList === virtualApi) {
|
|
2256
|
+
delete host.__dalilaVirtualList;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
1594
2259
|
for (const key of Array.from(clonesByKey.keys()))
|
|
1595
2260
|
removeKey(key);
|
|
1596
2261
|
topSpacer.remove();
|
|
@@ -1851,7 +2516,7 @@ function bindEach(root, ctx, cleanups) {
|
|
|
1851
2516
|
* Unlike [d-when] which toggles display, d-if adds/removes the element from
|
|
1852
2517
|
* the DOM entirely. A comment node is left as placeholder for insertion position.
|
|
1853
2518
|
*/
|
|
1854
|
-
function bindIf(root, ctx, cleanups) {
|
|
2519
|
+
function bindIf(root, ctx, cleanups, transitionRegistry) {
|
|
1855
2520
|
const elements = qsaIncludingRoot(root, '[d-if]');
|
|
1856
2521
|
const processedElse = new Set();
|
|
1857
2522
|
for (const el of elements) {
|
|
@@ -1869,23 +2534,36 @@ function bindIf(root, ctx, cleanups) {
|
|
|
1869
2534
|
el.parentNode?.replaceChild(comment, el);
|
|
1870
2535
|
el.removeAttribute('d-if');
|
|
1871
2536
|
const htmlEl = el;
|
|
2537
|
+
const transitions = createTransitionController(htmlEl, transitionRegistry, cleanups);
|
|
1872
2538
|
// Handle d-else branch
|
|
1873
2539
|
let elseHtmlEl = null;
|
|
1874
2540
|
let elseComment = null;
|
|
2541
|
+
let elseTransitions = null;
|
|
1875
2542
|
if (elseEl) {
|
|
1876
2543
|
processedElse.add(elseEl);
|
|
1877
2544
|
elseComment = document.createComment('d-else');
|
|
1878
2545
|
elseEl.parentNode?.replaceChild(elseComment, elseEl);
|
|
1879
2546
|
elseEl.removeAttribute('d-else');
|
|
1880
2547
|
elseHtmlEl = elseEl;
|
|
2548
|
+
elseTransitions = createTransitionController(elseHtmlEl, transitionRegistry, cleanups);
|
|
1881
2549
|
}
|
|
1882
2550
|
// Apply initial state synchronously to avoid FOUC
|
|
1883
2551
|
const initialValue = !!resolve(binding);
|
|
1884
2552
|
if (initialValue) {
|
|
1885
2553
|
comment.parentNode?.insertBefore(htmlEl, comment);
|
|
2554
|
+
syncPortalElement(htmlEl);
|
|
2555
|
+
if (transitions.hasTransition) {
|
|
2556
|
+
htmlEl.removeAttribute('data-leave');
|
|
2557
|
+
htmlEl.setAttribute('data-enter', '');
|
|
2558
|
+
}
|
|
1886
2559
|
}
|
|
1887
2560
|
else if (elseHtmlEl && elseComment) {
|
|
1888
2561
|
elseComment.parentNode?.insertBefore(elseHtmlEl, elseComment);
|
|
2562
|
+
syncPortalElement(elseHtmlEl);
|
|
2563
|
+
if (elseTransitions?.hasTransition) {
|
|
2564
|
+
elseHtmlEl.removeAttribute('data-leave');
|
|
2565
|
+
elseHtmlEl.setAttribute('data-enter', '');
|
|
2566
|
+
}
|
|
1889
2567
|
}
|
|
1890
2568
|
// Then create reactive effect to keep it updated
|
|
1891
2569
|
if (elseHtmlEl && elseComment) {
|
|
@@ -1896,18 +2574,26 @@ function bindIf(root, ctx, cleanups) {
|
|
|
1896
2574
|
if (value) {
|
|
1897
2575
|
if (!htmlEl.parentNode) {
|
|
1898
2576
|
comment.parentNode?.insertBefore(htmlEl, comment);
|
|
2577
|
+
syncPortalElement(htmlEl);
|
|
1899
2578
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2579
|
+
transitions.enter();
|
|
2580
|
+
elseTransitions?.leave(() => {
|
|
2581
|
+
if (capturedElseEl.parentNode) {
|
|
2582
|
+
capturedElseEl.parentNode.removeChild(capturedElseEl);
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
1903
2585
|
}
|
|
1904
2586
|
else {
|
|
1905
|
-
|
|
1906
|
-
htmlEl.parentNode
|
|
1907
|
-
|
|
2587
|
+
transitions.leave(() => {
|
|
2588
|
+
if (htmlEl.parentNode) {
|
|
2589
|
+
htmlEl.parentNode.removeChild(htmlEl);
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
1908
2592
|
if (!capturedElseEl.parentNode) {
|
|
1909
2593
|
capturedElseComment.parentNode?.insertBefore(capturedElseEl, capturedElseComment);
|
|
2594
|
+
syncPortalElement(capturedElseEl);
|
|
1910
2595
|
}
|
|
2596
|
+
elseTransitions?.enter();
|
|
1911
2597
|
}
|
|
1912
2598
|
});
|
|
1913
2599
|
}
|
|
@@ -1917,12 +2603,16 @@ function bindIf(root, ctx, cleanups) {
|
|
|
1917
2603
|
if (value) {
|
|
1918
2604
|
if (!htmlEl.parentNode) {
|
|
1919
2605
|
comment.parentNode?.insertBefore(htmlEl, comment);
|
|
2606
|
+
syncPortalElement(htmlEl);
|
|
1920
2607
|
}
|
|
2608
|
+
transitions.enter();
|
|
1921
2609
|
}
|
|
1922
2610
|
else {
|
|
1923
|
-
|
|
1924
|
-
htmlEl.parentNode
|
|
1925
|
-
|
|
2611
|
+
transitions.leave(() => {
|
|
2612
|
+
if (htmlEl.parentNode) {
|
|
2613
|
+
htmlEl.parentNode.removeChild(htmlEl);
|
|
2614
|
+
}
|
|
2615
|
+
});
|
|
1926
2616
|
}
|
|
1927
2617
|
});
|
|
1928
2618
|
}
|
|
@@ -3064,6 +3754,20 @@ function bindComponents(root, ctx, events, cleanups, onMountError) {
|
|
|
3064
3754
|
// Global Configuration
|
|
3065
3755
|
// ============================================================================
|
|
3066
3756
|
let globalConfig = {};
|
|
3757
|
+
export function createPortalTarget(id) {
|
|
3758
|
+
const targetSignal = signal(null);
|
|
3759
|
+
if (typeof document === 'undefined') {
|
|
3760
|
+
return targetSignal;
|
|
3761
|
+
}
|
|
3762
|
+
let target = document.getElementById(id);
|
|
3763
|
+
if (!target) {
|
|
3764
|
+
target = document.createElement('div');
|
|
3765
|
+
target.id = id;
|
|
3766
|
+
document.body.appendChild(target);
|
|
3767
|
+
}
|
|
3768
|
+
targetSignal.set(target);
|
|
3769
|
+
return targetSignal;
|
|
3770
|
+
}
|
|
3067
3771
|
/**
|
|
3068
3772
|
* Set global defaults for all `bind()` / `mount()` calls.
|
|
3069
3773
|
*
|
|
@@ -3116,8 +3820,8 @@ export function configure(config) {
|
|
|
3116
3820
|
export function bind(root, ctx, options = {}) {
|
|
3117
3821
|
// ── Merge global config with per-call options ──
|
|
3118
3822
|
if (Object.keys(globalConfig).length > 0) {
|
|
3119
|
-
const { components: globalComponents, ...globalRest } = globalConfig;
|
|
3120
|
-
const { components: localComponents, ...localRest } = options;
|
|
3823
|
+
const { components: globalComponents, transitions: globalTransitions, ...globalRest } = globalConfig;
|
|
3824
|
+
const { components: localComponents, transitions: localTransitions, ...localRest } = options;
|
|
3121
3825
|
const mergedOpts = { ...globalRest, ...localRest };
|
|
3122
3826
|
// Combine component registries: local takes precedence over global
|
|
3123
3827
|
if (globalComponents || localComponents) {
|
|
@@ -3142,6 +3846,20 @@ export function bind(root, ctx, options = {}) {
|
|
|
3142
3846
|
mergeComponents(localComponents); // local wins
|
|
3143
3847
|
mergedOpts.components = combined;
|
|
3144
3848
|
}
|
|
3849
|
+
if (globalTransitions || localTransitions) {
|
|
3850
|
+
const byName = new Map();
|
|
3851
|
+
for (const item of globalTransitions ?? []) {
|
|
3852
|
+
if (!item || typeof item.name !== 'string')
|
|
3853
|
+
continue;
|
|
3854
|
+
byName.set(item.name, item);
|
|
3855
|
+
}
|
|
3856
|
+
for (const item of localTransitions ?? []) {
|
|
3857
|
+
if (!item || typeof item.name !== 'string')
|
|
3858
|
+
continue;
|
|
3859
|
+
byName.set(item.name, item);
|
|
3860
|
+
}
|
|
3861
|
+
mergedOpts.transitions = Array.from(byName.values());
|
|
3862
|
+
}
|
|
3145
3863
|
options = mergedOpts;
|
|
3146
3864
|
}
|
|
3147
3865
|
// ── Resolve string selector ──
|
|
@@ -3186,6 +3904,7 @@ export function bind(root, ctx, options = {}) {
|
|
|
3186
3904
|
const onMountError = options.onMountError ?? 'log';
|
|
3187
3905
|
const rawTextSelectors = options.rawTextSelectors ?? DEFAULT_RAW_TEXT_SELECTORS;
|
|
3188
3906
|
const templatePlanCacheConfig = resolveTemplatePlanCacheConfig(options);
|
|
3907
|
+
const transitionRegistry = createTransitionRegistry(options.transitions);
|
|
3189
3908
|
const benchSession = createBindBenchSession();
|
|
3190
3909
|
const htmlRoot = root;
|
|
3191
3910
|
// HMR support: Register binding context globally in dev mode.
|
|
@@ -3229,14 +3948,16 @@ export function bind(root, ctx, options = {}) {
|
|
|
3229
3948
|
// 13. d-emit-* bindings (component template → parent)
|
|
3230
3949
|
bindEmit(root, ctx, cleanups);
|
|
3231
3950
|
// 14. d-when directive
|
|
3232
|
-
bindWhen(root, ctx, cleanups);
|
|
3951
|
+
bindWhen(root, ctx, cleanups, transitionRegistry);
|
|
3233
3952
|
// 15. d-match directive
|
|
3234
3953
|
bindMatch(root, ctx, cleanups);
|
|
3235
3954
|
// 16. Form error displays — BEFORE d-if to bind errors in conditionally rendered sections
|
|
3236
3955
|
bindError(root, ctx, cleanups);
|
|
3237
3956
|
bindFormError(root, ctx, cleanups);
|
|
3238
|
-
// 17. d-
|
|
3239
|
-
|
|
3957
|
+
// 17. d-portal — move already-bound elements to external targets
|
|
3958
|
+
bindPortal(root, ctx, cleanups);
|
|
3959
|
+
// 18. d-if — must run last: elements are fully bound before conditional removal
|
|
3960
|
+
bindIf(root, ctx, cleanups, transitionRegistry);
|
|
3240
3961
|
});
|
|
3241
3962
|
// Bindings complete: remove loading state and mark as ready.
|
|
3242
3963
|
// Only the top-level bind owns this lifecycle — d-each clones skip it.
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module dalila/runtime
|
|
8
8
|
*/
|
|
9
|
-
export { bind, autoBind, mount, configure } from './bind.js';
|
|
10
|
-
export type { BindOptions, BindContext, BindData, DisposeFunction, BindHandle } from './bind.js';
|
|
9
|
+
export { bind, autoBind, mount, configure, createPortalTarget, getVirtualListController, scrollToVirtualIndex } from './bind.js';
|
|
10
|
+
export type { BindOptions, BindContext, BindData, DisposeFunction, BindHandle, TransitionConfig, VirtualListAlign, VirtualScrollToIndexOptions, VirtualListController } from './bind.js';
|
|
11
11
|
export { fromHtml } from './fromHtml.js';
|
|
12
12
|
export type { FromHtmlOptions } from './fromHtml.js';
|
|
13
13
|
export { defineComponent } from './component.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module dalila/runtime
|
|
8
8
|
*/
|
|
9
|
-
export { bind, autoBind, mount, configure } from './bind.js';
|
|
9
|
+
export { bind, autoBind, mount, configure, createPortalTarget, getVirtualListController, scrollToVirtualIndex } from './bind.js';
|
|
10
10
|
export { fromHtml } from './fromHtml.js';
|
|
11
11
|
export { defineComponent } from './component.js';
|