dalila 1.9.2 → 1.9.4

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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Dalila Component System
3
+ *
4
+ * Declarative component definitions for use with bind().
5
+ * This module contains only types, defineComponent, and pure helpers —
6
+ * no imports from bind.ts (avoids circular dependency).
7
+ *
8
+ * @module dalila/runtime/component
9
+ */
10
+ import type { Signal } from '../core/signal.js';
11
+ export type PropConstructor = StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | FunctionConstructor;
12
+ export interface PropDefinition {
13
+ type: PropConstructor;
14
+ required?: boolean;
15
+ default?: unknown;
16
+ }
17
+ export type PropOption = PropConstructor | PropDefinition;
18
+ export type PropsSchema = Record<string, PropOption>;
19
+ type InferPropType<T extends PropConstructor> = T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? unknown[] : T extends ObjectConstructor ? Record<string, unknown> : T extends FunctionConstructor ? Function : unknown;
20
+ type InferPropOptionType<T extends PropOption> = T extends PropConstructor ? InferPropType<T> : T extends {
21
+ type: infer C extends PropConstructor;
22
+ default: infer D;
23
+ } ? D extends (...args: any[]) => infer R ? R : InferPropType<C> : T extends {
24
+ type: infer C extends PropConstructor;
25
+ } ? InferPropType<C> : unknown;
26
+ export type TypedPropSignals<P extends PropsSchema> = {
27
+ [K in keyof P]: Signal<InferPropOptionType<P[K]>>;
28
+ };
29
+ /** @deprecated Use TypedPropSignals instead */
30
+ export type PropSignals<P> = {
31
+ [K in keyof P]: Signal<unknown>;
32
+ };
33
+ export type EmitsSchema = Record<string, unknown>;
34
+ export type RefsSchema = Record<string, Element>;
35
+ export type TypedEmit<E extends EmitsSchema> = <K extends keyof E & string>(event: K, payload: E[K]) => void;
36
+ export type TypedRef<R extends RefsSchema> = <K extends keyof R & string>(name: K) => R[K] | null;
37
+ export interface TypedSetupContext<E extends EmitsSchema = EmitsSchema, R extends RefsSchema = RefsSchema> {
38
+ ref: TypedRef<R>;
39
+ refs(): Readonly<Partial<R>>;
40
+ emit: TypedEmit<E>;
41
+ onMount(fn: () => void): void;
42
+ onCleanup(fn: () => void): void;
43
+ }
44
+ /** @deprecated Use TypedSetupContext instead */
45
+ export interface SetupContext {
46
+ ref(name: string): Element | null;
47
+ refs(): Readonly<Record<string, Element>>;
48
+ emit(event: string, ...args: unknown[]): void;
49
+ onMount(fn: () => void): void;
50
+ onCleanup(fn: () => void): void;
51
+ }
52
+ export interface ComponentDefinition<P extends PropsSchema = PropsSchema, E extends EmitsSchema = EmitsSchema, R extends RefsSchema = RefsSchema> {
53
+ tag: string;
54
+ template: string;
55
+ props?: P;
56
+ setup?: (props: TypedPropSignals<P>, ctx: TypedSetupContext<E, R>) => Record<string, unknown>;
57
+ }
58
+ /**
59
+ * Erased component handle used by bind() and mount().
60
+ * The generic P from defineComponent is erased here so that
61
+ * any Component can be stored in a Record<string, Component> registry
62
+ * without variance issues.
63
+ */
64
+ export interface Component {
65
+ readonly __dalila_component: true;
66
+ readonly definition: ComponentDefinition<any, any, any>;
67
+ }
68
+ export declare function defineComponent<P extends PropsSchema = PropsSchema, E extends EmitsSchema = EmitsSchema, R extends RefsSchema = RefsSchema>(def: ComponentDefinition<P, E, R>): Component;
69
+ export declare function isComponent(value: unknown): value is Component;
70
+ export declare function normalizePropDef(option: PropOption): PropDefinition;
71
+ export declare function coercePropValue(raw: string, type: PropConstructor): unknown;
72
+ export declare function kebabToCamel(str: string): string;
73
+ export declare function camelToKebab(str: string): string;
74
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Dalila Component System
3
+ *
4
+ * Declarative component definitions for use with bind().
5
+ * This module contains only types, defineComponent, and pure helpers —
6
+ * no imports from bind.ts (avoids circular dependency).
7
+ *
8
+ * @module dalila/runtime/component
9
+ */
10
+ // ============================================================================
11
+ // defineComponent
12
+ // ============================================================================
13
+ export function defineComponent(def) {
14
+ if (!def.tag || !def.tag.includes('-')) {
15
+ throw new Error(`[Dalila] defineComponent: tag "${def.tag}" must contain a hyphen.`);
16
+ }
17
+ return { __dalila_component: true, definition: def };
18
+ }
19
+ export function isComponent(value) {
20
+ return typeof value === 'object' && value !== null && value.__dalila_component === true;
21
+ }
22
+ // ============================================================================
23
+ // Prop Helpers
24
+ // ============================================================================
25
+ export function normalizePropDef(option) {
26
+ return typeof option === 'function' ? { type: option } : option;
27
+ }
28
+ export function coercePropValue(raw, type) {
29
+ switch (type) {
30
+ case Number: return Number(raw);
31
+ case Boolean: return raw !== 'false' && raw !== '0';
32
+ default: return raw;
33
+ }
34
+ }
35
+ export function kebabToCamel(str) {
36
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
37
+ }
38
+ export function camelToKebab(str) {
39
+ return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
40
+ }
@@ -9,9 +9,9 @@
9
9
  * @module dalila/runtime
10
10
  */
11
11
  import type { Scope } from '../core/scope.js';
12
- export interface FromHtmlOptions {
12
+ export interface FromHtmlOptions<T extends Record<string, unknown> = Record<string, unknown>> {
13
13
  /** Bind context — keys map to {placeholder} tokens in the HTML */
14
- data?: Record<string, unknown>;
14
+ data?: T;
15
15
  /** Child nodes to inject into [data-slot="children"] */
16
16
  children?: Node | DocumentFragment | Node[];
17
17
  /** Route scope — registers bind cleanup automatically */
@@ -32,4 +32,5 @@ export interface FromHtmlOptions {
32
32
  * const el = fromHtml('<div><div data-slot="children"></div></div>', { children });
33
33
  * ```
34
34
  */
35
+ export declare function fromHtml<T extends Record<string, unknown>>(html: string, options: FromHtmlOptions<T>): HTMLElement;
35
36
  export declare function fromHtml(html: string, options?: FromHtmlOptions): HTMLElement;
@@ -9,21 +9,6 @@
9
9
  * @module dalila/runtime
10
10
  */
11
11
  import { bind } from './bind.js';
12
- /**
13
- * Parse an HTML string into a bound DOM element.
14
- *
15
- * @example
16
- * ```ts
17
- * // Static HTML
18
- * const el = fromHtml('<div><h1>Hello</h1></div>');
19
- *
20
- * // With data binding
21
- * const el = fromHtml('<div>{name}</div>', { data: { name: 'Dalila' } });
22
- *
23
- * // Layout with children slot
24
- * const el = fromHtml('<div><div data-slot="children"></div></div>', { children });
25
- * ```
26
- */
27
12
  export function fromHtml(html, options = {}) {
28
13
  const { data, children, scope } = options;
29
14
  const template = document.createElement('template');
@@ -6,7 +6,9 @@
6
6
  *
7
7
  * @module dalila/runtime
8
8
  */
9
- export { bind, autoBind } from './bind.js';
10
- export type { BindOptions, BindContext, DisposeFunction } from './bind.js';
9
+ export { bind, autoBind, mount, configure } from './bind.js';
10
+ export type { BindOptions, BindContext, BindData, DisposeFunction, BindHandle } from './bind.js';
11
11
  export { fromHtml } from './fromHtml.js';
12
12
  export type { FromHtmlOptions } from './fromHtml.js';
13
+ export { defineComponent } from './component.js';
14
+ export type { Component, ComponentDefinition, PropsSchema, PropSignals, SetupContext, TypedPropSignals, TypedSetupContext, EmitsSchema, RefsSchema, TypedEmit, TypedRef, } from './component.js';
@@ -6,5 +6,6 @@
6
6
  *
7
7
  * @module dalila/runtime
8
8
  */
9
- export { bind, autoBind } from './bind.js';
9
+ export { bind, autoBind, mount, configure } from './bind.js';
10
10
  export { fromHtml } from './fromHtml.js';
11
+ export { defineComponent } from './component.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -100,7 +100,7 @@
100
100
  "build": "tsc && node scripts/ensure-cli-executable.cjs",
101
101
  "dev": "tsc --watch",
102
102
  "serve": "node scripts/dev-server.cjs",
103
- "test": "npm run build && node --test",
103
+ "test": "npm run build && node --test --test-concurrency=1 test/*.test.js",
104
104
  "test:e2e": "npm run build && playwright test",
105
105
  "test:watch": "jest --watch",
106
106
  "clean": "rm -rf dist",
@@ -718,6 +718,12 @@ const server = http.createServer((req, res) => {
718
718
  const effectivePath =
719
719
  requestPath === '/' || requestPath === '/index.html' ? defaultEntry : requestPath;
720
720
  let resolvedRequestPath = effectivePath;
721
+ const fetchModeHeader = req.headers['sec-fetch-mode'];
722
+ const fetchDestHeader = req.headers['sec-fetch-dest'];
723
+ const fetchMode = Array.isArray(fetchModeHeader) ? fetchModeHeader[0] : fetchModeHeader;
724
+ const fetchDest = Array.isArray(fetchDestHeader) ? fetchDestHeader[0] : fetchDestHeader;
725
+ const isNavigationRequest = fetchMode === 'navigate' || fetchDest === 'document';
726
+ const isScriptRequest = fetchDest === 'script';
721
727
  const fsPath = resolvePath(effectivePath);
722
728
 
723
729
  if (!fsPath) {
@@ -748,17 +754,48 @@ const server = http.createServer((req, res) => {
748
754
  return;
749
755
  }
750
756
  } else {
751
- const spaFallback = resolveSpaFallbackPath(requestPath);
752
- if (spaFallback) {
753
- targetPath = spaFallback.fsPath;
754
- resolvedRequestPath = spaFallback.requestPath;
757
+ // Extensionless import — try .ts, then .js
758
+ const tsPath = targetPath + '.ts';
759
+ const jsPath = targetPath + '.js';
760
+ if (!path.extname(targetPath) && isScriptRequest && fs.existsSync(tsPath)) {
761
+ targetPath = tsPath;
762
+ } else if (!path.extname(targetPath) && isScriptRequest && fs.existsSync(jsPath)) {
763
+ targetPath = jsPath;
755
764
  } else {
756
- send(res, 404, 'Not Found');
757
- return;
765
+ const spaFallback = resolveSpaFallbackPath(requestPath);
766
+ if (spaFallback) {
767
+ targetPath = spaFallback.fsPath;
768
+ resolvedRequestPath = spaFallback.requestPath;
769
+ } else {
770
+ send(res, 404, 'Not Found');
771
+ return;
772
+ }
758
773
  }
759
774
  }
760
775
  }
761
776
 
777
+ // Raw import — serve file as `export default` JS module.
778
+ // Triggered by ?raw suffix OR .html imported as script/module.
779
+ const isRawQuery = req.url && req.url.includes('?raw');
780
+ const isHtmlModuleImport = targetPath.endsWith('.html')
781
+ && isScriptRequest
782
+ && !isNavigationRequest;
783
+ if (isRawQuery || isHtmlModuleImport) {
784
+ fs.readFile(targetPath, 'utf8', (err, source) => {
785
+ if (err) {
786
+ send(res, err.code === 'ENOENT' ? 404 : 500, err.code === 'ENOENT' ? 'Not Found' : 'Error');
787
+ return;
788
+ }
789
+ const escaped = source.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
790
+ res.writeHead(200, {
791
+ 'Content-Type': 'text/javascript; charset=utf-8',
792
+ 'Cache-Control': 'no-store, no-cache, must-revalidate',
793
+ });
794
+ res.end(`export default \`${escaped}\`;`);
795
+ });
796
+ return;
797
+ }
798
+
762
799
  // TypeScript transpilation (only if ts available)
763
800
  if (targetPath.endsWith('.ts') && ts) {
764
801
  fs.readFile(targetPath, 'utf8', (err, source) => {
@@ -1009,5 +1046,8 @@ setupWatcher();
1009
1046
  startKeepalive();
1010
1047
 
1011
1048
  server.listen(port, () => {
1012
- console.log(`Dalila dev server on http://localhost:${port}`);
1049
+ console.log('');
1050
+ console.log(' 🐰 ✂️ Dalila dev server');
1051
+ console.log(` http://localhost:${port}`);
1052
+ console.log('');
1013
1053
  });