dalila 1.9.4 → 1.9.5

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
@@ -1,4 +1,4 @@
1
- # 🐰 ✂️ Dalila
1
+ # 🐰✂️ Dalila
2
2
 
3
3
  **DOM-first reactivity without the re-renders.**
4
4
 
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-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-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))) {
package/dist/cli/index.js CHANGED
@@ -9,7 +9,7 @@ const routeArgs = args.slice(2);
9
9
  const WATCH_DEBOUNCE_MS = 120;
10
10
  function showHelp() {
11
11
  console.log(`
12
- 🐰 ✂️ Dalila CLI
12
+ 🐰✂️ Dalila CLI
13
13
 
14
14
  Usage:
15
15
  dalila routes generate [options] Generate routes + manifest from app file structure
@@ -32,7 +32,7 @@ Examples:
32
32
  }
33
33
  function showRoutesHelp() {
34
34
  console.log(`
35
- 🐰 ✂️ Dalila CLI - Routes
35
+ 🐰✂️ Dalila CLI - Routes
36
36
 
37
37
  Usage:
38
38
  dalila routes generate [options] Generate routes + manifest from app file structure
@@ -52,7 +52,7 @@ Examples:
52
52
  }
53
53
  function showCheckHelp() {
54
54
  console.log(`
55
- 🐰 ✂️ Dalila CLI - Check
55
+ 🐰✂️ Dalila CLI - Check
56
56
 
57
57
  Usage:
58
58
  dalila check [path] [options] Static analysis of HTML templates
@@ -182,7 +182,7 @@ function resolveGenerateConfig(cliArgs, cwd = process.cwd()) {
182
182
  async function generateRoutes(cliArgs) {
183
183
  const { appDir, outputPath } = resolveGenerateConfig(cliArgs);
184
184
  console.log('');
185
- console.log('🐰 ✂️ Dalila Routes Generator');
185
+ console.log('🐰✂️ Dalila Routes Generator');
186
186
  console.log('');
187
187
  try {
188
188
  await generateRoutesFile(appDir, outputPath);
@@ -243,7 +243,7 @@ function watchRoutes(cliArgs) {
243
243
  process.exit(1);
244
244
  }
245
245
  console.log('');
246
- console.log('🐰 ✂️ Dalila Routes Watch');
246
+ console.log('🐰✂️ Dalila Routes Watch');
247
247
  console.log(` app: ${appDir}`);
248
248
  console.log(` output: ${outputPath}`);
249
249
  console.log('');
@@ -2068,14 +2068,76 @@ function bindAttrs(root, ctx, cleanups) {
2068
2068
  // d-bind-* Directive (Two-way Binding)
2069
2069
  // ============================================================================
2070
2070
  /**
2071
- * Bind all [d-bind-value] and [d-bind-checked] directives within root.
2072
- * Two-way binding: signal → DOM (outbound) and DOM → signal (inbound).
2073
- * Only works with signals — logs a warning otherwise.
2071
+ * Bind all [d-bind-*] directives within root.
2072
+ * Two-way bindings:
2073
+ * - d-bind-value
2074
+ * - d-bind-checked
2075
+ *
2076
+ * One-way reactive property bindings:
2077
+ * - d-bind-readonly
2078
+ * - d-bind-disabled
2079
+ * - d-bind-maxlength
2080
+ * - d-bind-placeholder
2081
+ * - d-bind-pattern
2082
+ * - d-bind-multiple
2083
+ *
2084
+ * Optional transform/parse hooks:
2085
+ * - d-bind-transform="fnName" (signal -> view)
2086
+ * - d-bind-parse="fnName" (view -> signal)
2074
2087
  */
2075
2088
  function bindTwoWay(root, ctx, cleanups) {
2076
- const SUPPORTED = ['value', 'checked'];
2077
- for (const prop of SUPPORTED) {
2078
- const attr = `d-bind-${prop}`;
2089
+ const SUPPORTED = [
2090
+ { attr: 'd-bind-value', prop: 'value', twoWay: true },
2091
+ { attr: 'd-bind-checked', prop: 'checked', twoWay: true },
2092
+ { attr: 'd-bind-readonly', prop: 'readOnly', twoWay: false },
2093
+ { attr: 'd-bind-disabled', prop: 'disabled', twoWay: false },
2094
+ { attr: 'd-bind-maxlength', prop: 'maxLength', twoWay: false },
2095
+ { attr: 'd-bind-placeholder', prop: 'placeholder', twoWay: false },
2096
+ { attr: 'd-bind-pattern', prop: 'pattern', twoWay: false },
2097
+ { attr: 'd-bind-multiple', prop: 'multiple', twoWay: false },
2098
+ ];
2099
+ const BOOLEAN_PROPS = new Set(['checked', 'readOnly', 'disabled', 'multiple']);
2100
+ const STRING_PROPS = new Set(['value', 'placeholder', 'pattern']);
2101
+ const applyBoundProp = (el, prop, value) => {
2102
+ if (!(prop in el))
2103
+ return;
2104
+ if (BOOLEAN_PROPS.has(prop)) {
2105
+ el[prop] = !!value;
2106
+ return;
2107
+ }
2108
+ if (STRING_PROPS.has(prop)) {
2109
+ el[prop] = value == null ? '' : String(value);
2110
+ return;
2111
+ }
2112
+ if (prop === 'maxLength') {
2113
+ if (value == null || value === '') {
2114
+ el.maxLength = -1;
2115
+ return;
2116
+ }
2117
+ const parsed = Number(value);
2118
+ el.maxLength = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : -1;
2119
+ return;
2120
+ }
2121
+ el[prop] = value;
2122
+ };
2123
+ const resolveFunctionBinding = (el, attrName) => {
2124
+ const fnBindingName = normalizeBinding(el.getAttribute(attrName));
2125
+ if (!fnBindingName)
2126
+ return null;
2127
+ const fnBinding = ctx[fnBindingName];
2128
+ if (fnBinding === undefined) {
2129
+ warn(`${attrName}: "${fnBindingName}" not found in context`);
2130
+ return null;
2131
+ }
2132
+ const resolved = isSignal(fnBinding) ? fnBinding() : fnBinding;
2133
+ if (typeof resolved !== 'function') {
2134
+ warn(`${attrName}: "${fnBindingName}" must be a function (or signal-of-function)`);
2135
+ return null;
2136
+ }
2137
+ return resolved;
2138
+ };
2139
+ for (const directive of SUPPORTED) {
2140
+ const attr = directive.attr;
2079
2141
  const elements = qsaIncludingRoot(root, `[${attr}]`);
2080
2142
  for (const el of elements) {
2081
2143
  const bindingName = normalizeBinding(el.getAttribute(attr));
@@ -2083,31 +2145,55 @@ function bindTwoWay(root, ctx, cleanups) {
2083
2145
  continue;
2084
2146
  const binding = ctx[bindingName];
2085
2147
  if (!isSignal(binding)) {
2086
- warn(`d-bind-${prop}: "${bindingName}" must be a signal`);
2148
+ warn(`${attr}: "${bindingName}" must be a signal`);
2087
2149
  continue;
2088
2150
  }
2089
2151
  const writable = isWritableSignal(binding);
2090
- if (!writable) {
2091
- warn(`d-bind-${prop}: "${bindingName}" is read-only (inbound updates disabled)`);
2152
+ if (directive.twoWay && !writable) {
2153
+ warn(`${attr}: "${bindingName}" is read-only (inbound updates disabled)`);
2092
2154
  }
2093
2155
  el.removeAttribute(attr);
2156
+ const transformFn = resolveFunctionBinding(el, 'd-bind-transform');
2157
+ const parseFn = resolveFunctionBinding(el, 'd-bind-parse');
2158
+ if (directive.twoWay) {
2159
+ el.removeAttribute('d-bind-transform');
2160
+ el.removeAttribute('d-bind-parse');
2161
+ }
2094
2162
  // Outbound: signal → DOM
2095
- const isBoolean = prop === 'checked';
2096
2163
  bindEffect(el, () => {
2097
- const val = binding();
2098
- if (isBoolean) {
2099
- el[prop] = !!val;
2100
- }
2101
- else {
2102
- el[prop] = val == null ? '' : String(val);
2164
+ const rawValue = binding();
2165
+ let value = rawValue;
2166
+ if (transformFn) {
2167
+ try {
2168
+ value = transformFn(rawValue, el);
2169
+ }
2170
+ catch (err) {
2171
+ warn(`d-bind-transform: "${bindingName}" failed (${err.message || String(err)})`);
2172
+ value = rawValue;
2173
+ }
2103
2174
  }
2175
+ applyBoundProp(el, directive.prop, value);
2104
2176
  });
2105
2177
  // Inbound: DOM → signal
2106
- if (writable) {
2107
- const eventName = el.tagName === 'SELECT' || isBoolean ? 'change' : 'input';
2178
+ if (directive.twoWay && writable) {
2179
+ const eventName = el.tagName === 'SELECT' || directive.prop === 'checked'
2180
+ ? 'change'
2181
+ : 'input';
2108
2182
  const handler = () => {
2109
- const val = isBoolean ? el.checked : el.value;
2110
- binding.set(val);
2183
+ const rawValue = directive.prop === 'checked'
2184
+ ? el.checked
2185
+ : el.value;
2186
+ let nextValue = rawValue;
2187
+ if (parseFn) {
2188
+ try {
2189
+ nextValue = parseFn(rawValue, el);
2190
+ }
2191
+ catch (err) {
2192
+ warn(`d-bind-parse: "${bindingName}" failed (${err.message || String(err)})`);
2193
+ nextValue = rawValue;
2194
+ }
2195
+ }
2196
+ binding.set(nextValue);
2111
2197
  };
2112
2198
  el.addEventListener(eventName, handler);
2113
2199
  cleanups.push(() => el.removeEventListener(eventName, handler));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1047,7 +1047,7 @@ startKeepalive();
1047
1047
 
1048
1048
  server.listen(port, () => {
1049
1049
  console.log('');
1050
- console.log(' 🐰 ✂️ Dalila dev server');
1050
+ console.log(' 🐰✂️ Dalila dev server');
1051
1051
  console.log(` http://localhost:${port}`);
1052
1052
  console.log('');
1053
1053
  });