dalila 1.9.5 → 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
 
@@ -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-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))) {
@@ -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,13 @@ 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 declare function createPortalTarget(id: string): Signal<Element | null>;
62
74
  /**
63
75
  * Set global defaults for all `bind()` / `mount()` calls.
64
76
  *
@@ -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
- htmlEl.style.display = initialValue ? '' : 'none';
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
- htmlEl.style.display = value ? '' : 'none';
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
  }
@@ -1851,7 +2089,7 @@ function bindEach(root, ctx, cleanups) {
1851
2089
  * Unlike [d-when] which toggles display, d-if adds/removes the element from
1852
2090
  * the DOM entirely. A comment node is left as placeholder for insertion position.
1853
2091
  */
1854
- function bindIf(root, ctx, cleanups) {
2092
+ function bindIf(root, ctx, cleanups, transitionRegistry) {
1855
2093
  const elements = qsaIncludingRoot(root, '[d-if]');
1856
2094
  const processedElse = new Set();
1857
2095
  for (const el of elements) {
@@ -1869,23 +2107,36 @@ function bindIf(root, ctx, cleanups) {
1869
2107
  el.parentNode?.replaceChild(comment, el);
1870
2108
  el.removeAttribute('d-if');
1871
2109
  const htmlEl = el;
2110
+ const transitions = createTransitionController(htmlEl, transitionRegistry, cleanups);
1872
2111
  // Handle d-else branch
1873
2112
  let elseHtmlEl = null;
1874
2113
  let elseComment = null;
2114
+ let elseTransitions = null;
1875
2115
  if (elseEl) {
1876
2116
  processedElse.add(elseEl);
1877
2117
  elseComment = document.createComment('d-else');
1878
2118
  elseEl.parentNode?.replaceChild(elseComment, elseEl);
1879
2119
  elseEl.removeAttribute('d-else');
1880
2120
  elseHtmlEl = elseEl;
2121
+ elseTransitions = createTransitionController(elseHtmlEl, transitionRegistry, cleanups);
1881
2122
  }
1882
2123
  // Apply initial state synchronously to avoid FOUC
1883
2124
  const initialValue = !!resolve(binding);
1884
2125
  if (initialValue) {
1885
2126
  comment.parentNode?.insertBefore(htmlEl, comment);
2127
+ syncPortalElement(htmlEl);
2128
+ if (transitions.hasTransition) {
2129
+ htmlEl.removeAttribute('data-leave');
2130
+ htmlEl.setAttribute('data-enter', '');
2131
+ }
1886
2132
  }
1887
2133
  else if (elseHtmlEl && elseComment) {
1888
2134
  elseComment.parentNode?.insertBefore(elseHtmlEl, elseComment);
2135
+ syncPortalElement(elseHtmlEl);
2136
+ if (elseTransitions?.hasTransition) {
2137
+ elseHtmlEl.removeAttribute('data-leave');
2138
+ elseHtmlEl.setAttribute('data-enter', '');
2139
+ }
1889
2140
  }
1890
2141
  // Then create reactive effect to keep it updated
1891
2142
  if (elseHtmlEl && elseComment) {
@@ -1896,18 +2147,26 @@ function bindIf(root, ctx, cleanups) {
1896
2147
  if (value) {
1897
2148
  if (!htmlEl.parentNode) {
1898
2149
  comment.parentNode?.insertBefore(htmlEl, comment);
2150
+ syncPortalElement(htmlEl);
1899
2151
  }
1900
- if (capturedElseEl.parentNode) {
1901
- capturedElseEl.parentNode.removeChild(capturedElseEl);
1902
- }
2152
+ transitions.enter();
2153
+ elseTransitions?.leave(() => {
2154
+ if (capturedElseEl.parentNode) {
2155
+ capturedElseEl.parentNode.removeChild(capturedElseEl);
2156
+ }
2157
+ });
1903
2158
  }
1904
2159
  else {
1905
- if (htmlEl.parentNode) {
1906
- htmlEl.parentNode.removeChild(htmlEl);
1907
- }
2160
+ transitions.leave(() => {
2161
+ if (htmlEl.parentNode) {
2162
+ htmlEl.parentNode.removeChild(htmlEl);
2163
+ }
2164
+ });
1908
2165
  if (!capturedElseEl.parentNode) {
1909
2166
  capturedElseComment.parentNode?.insertBefore(capturedElseEl, capturedElseComment);
2167
+ syncPortalElement(capturedElseEl);
1910
2168
  }
2169
+ elseTransitions?.enter();
1911
2170
  }
1912
2171
  });
1913
2172
  }
@@ -1917,12 +2176,16 @@ function bindIf(root, ctx, cleanups) {
1917
2176
  if (value) {
1918
2177
  if (!htmlEl.parentNode) {
1919
2178
  comment.parentNode?.insertBefore(htmlEl, comment);
2179
+ syncPortalElement(htmlEl);
1920
2180
  }
2181
+ transitions.enter();
1921
2182
  }
1922
2183
  else {
1923
- if (htmlEl.parentNode) {
1924
- htmlEl.parentNode.removeChild(htmlEl);
1925
- }
2184
+ transitions.leave(() => {
2185
+ if (htmlEl.parentNode) {
2186
+ htmlEl.parentNode.removeChild(htmlEl);
2187
+ }
2188
+ });
1926
2189
  }
1927
2190
  });
1928
2191
  }
@@ -3064,6 +3327,20 @@ function bindComponents(root, ctx, events, cleanups, onMountError) {
3064
3327
  // Global Configuration
3065
3328
  // ============================================================================
3066
3329
  let globalConfig = {};
3330
+ export function createPortalTarget(id) {
3331
+ const targetSignal = signal(null);
3332
+ if (typeof document === 'undefined') {
3333
+ return targetSignal;
3334
+ }
3335
+ let target = document.getElementById(id);
3336
+ if (!target) {
3337
+ target = document.createElement('div');
3338
+ target.id = id;
3339
+ document.body.appendChild(target);
3340
+ }
3341
+ targetSignal.set(target);
3342
+ return targetSignal;
3343
+ }
3067
3344
  /**
3068
3345
  * Set global defaults for all `bind()` / `mount()` calls.
3069
3346
  *
@@ -3116,8 +3393,8 @@ export function configure(config) {
3116
3393
  export function bind(root, ctx, options = {}) {
3117
3394
  // ── Merge global config with per-call options ──
3118
3395
  if (Object.keys(globalConfig).length > 0) {
3119
- const { components: globalComponents, ...globalRest } = globalConfig;
3120
- const { components: localComponents, ...localRest } = options;
3396
+ const { components: globalComponents, transitions: globalTransitions, ...globalRest } = globalConfig;
3397
+ const { components: localComponents, transitions: localTransitions, ...localRest } = options;
3121
3398
  const mergedOpts = { ...globalRest, ...localRest };
3122
3399
  // Combine component registries: local takes precedence over global
3123
3400
  if (globalComponents || localComponents) {
@@ -3142,6 +3419,20 @@ export function bind(root, ctx, options = {}) {
3142
3419
  mergeComponents(localComponents); // local wins
3143
3420
  mergedOpts.components = combined;
3144
3421
  }
3422
+ if (globalTransitions || localTransitions) {
3423
+ const byName = new Map();
3424
+ for (const item of globalTransitions ?? []) {
3425
+ if (!item || typeof item.name !== 'string')
3426
+ continue;
3427
+ byName.set(item.name, item);
3428
+ }
3429
+ for (const item of localTransitions ?? []) {
3430
+ if (!item || typeof item.name !== 'string')
3431
+ continue;
3432
+ byName.set(item.name, item);
3433
+ }
3434
+ mergedOpts.transitions = Array.from(byName.values());
3435
+ }
3145
3436
  options = mergedOpts;
3146
3437
  }
3147
3438
  // ── Resolve string selector ──
@@ -3186,6 +3477,7 @@ export function bind(root, ctx, options = {}) {
3186
3477
  const onMountError = options.onMountError ?? 'log';
3187
3478
  const rawTextSelectors = options.rawTextSelectors ?? DEFAULT_RAW_TEXT_SELECTORS;
3188
3479
  const templatePlanCacheConfig = resolveTemplatePlanCacheConfig(options);
3480
+ const transitionRegistry = createTransitionRegistry(options.transitions);
3189
3481
  const benchSession = createBindBenchSession();
3190
3482
  const htmlRoot = root;
3191
3483
  // HMR support: Register binding context globally in dev mode.
@@ -3229,14 +3521,16 @@ export function bind(root, ctx, options = {}) {
3229
3521
  // 13. d-emit-* bindings (component template → parent)
3230
3522
  bindEmit(root, ctx, cleanups);
3231
3523
  // 14. d-when directive
3232
- bindWhen(root, ctx, cleanups);
3524
+ bindWhen(root, ctx, cleanups, transitionRegistry);
3233
3525
  // 15. d-match directive
3234
3526
  bindMatch(root, ctx, cleanups);
3235
3527
  // 16. Form error displays — BEFORE d-if to bind errors in conditionally rendered sections
3236
3528
  bindError(root, ctx, cleanups);
3237
3529
  bindFormError(root, ctx, cleanups);
3238
- // 17. d-ifmust run last: elements are fully bound before conditional removal
3239
- bindIf(root, ctx, cleanups);
3530
+ // 17. d-portalmove already-bound elements to external targets
3531
+ bindPortal(root, ctx, cleanups);
3532
+ // 18. d-if — must run last: elements are fully bound before conditional removal
3533
+ bindIf(root, ctx, cleanups, transitionRegistry);
3240
3534
  });
3241
3535
  // Bindings complete: remove loading state and mark as ready.
3242
3536
  // Only the top-level bind owns this lifecycle — d-each clones skip it.
@@ -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 } from './bind.js';
10
+ export type { BindOptions, BindContext, BindData, DisposeFunction, BindHandle, TransitionConfig } 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';
@@ -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 } from './bind.js';
10
10
  export { fromHtml } from './fromHtml.js';
11
11
  export { defineComponent } from './component.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.9.5",
3
+ "version": "1.9.6",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",