@marko/runtime-tags 6.3.2 → 6.3.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.
package/cheatsheet.md ADDED
@@ -0,0 +1,146 @@
1
+ # Marko 6 cheat sheet
2
+
3
+ Marko 6 = HTML superset. NOT JSX, NOT old Marko 4/5. `.marko` files are components; the filename is the tag name.
4
+
5
+ ## Golden rules
6
+
7
+ 1. Text interpolation: `${expr}` inside tag bodies. A bare line like `Welcome aboard` at the root of the template parses as a TAG named `Welcome` (concise mode) and fails to compile. Wrap it in an element (`<p>Welcome aboard</p>`) or prefix the line with `-- ` to mark it as text (`-- Welcome ${name}` works at the top level). Attributes take raw JS after `=` with NO braces/quotes needed: `<div title=user.name data-n=1 + 1>` (parenthesize if the value contains `>`).
8
+ 2. State: `<let/name=initial>` (slash then var name!). Update by plain assignment in an event handler: `count++`, `text = "hi"`. No setState, no hooks.
9
+ 3. Derived values: `<const/total=items.length * price>` — auto-recomputes. Never use an effect to derive state.
10
+ 4. NEVER mutate state in place. `items.push(x)` will NOT update the UI. Always reassign:
11
+ - add: `items = items.concat(x)`
12
+ - remove: `items = items.toSpliced(i, 1)`
13
+ - update: `items = items.toSpliced(i, 1, { ...item, done: true })`
14
+ - object: `user = { ...user, name }`
15
+ 5. Events: method shorthand `onClick() { ... }` or `onClick=fn`. Handler gets the DOM event: `onSubmit(e) { e.preventDefault(); save() }`. Don't sync input values through `onInput`/`onChange` listeners — that's what change handlers (next rule) are for, and they make the data's owner explicit.
16
+ 6. Native inputs are UNCONTROLLED by default: `value=` only sets the initial value. Adding the matching `*Change` handler is what makes them controlled — `valueChange` on `<input>`/`<textarea>`/`<select>`, `checkedChange` on checkboxes/radios, `openChange` on `<details>`/`<dialog>`. `value:=text` is the shorthand for `value=text valueChange(v) { text = v }`. (`<textarea value:=text/>` — value attribute, not body.)
17
+ 7. Write the change handler yourself when updates need transforming — number inputs give STRINGS: `<input type="number" value=n valueChange(v) { n = +v }>`.
18
+
19
+ ## Canonical component (copy this shape)
20
+
21
+ ```marko
22
+ <let/items=[]>
23
+ <let/draft="">
24
+ <const/remaining=items.filter(t => !t.done).length>
25
+
26
+ <input value:=draft placeholder="What next?">
27
+ <button onClick() {
28
+ const text = draft.trim();
29
+ if (text) {
30
+ items = items.concat({ id: items.length + 1, text, done: false });
31
+ draft = "";
32
+ }
33
+ }>Add</button>
34
+
35
+ <if=items.length>
36
+ <ul>
37
+ <for|item, i| of=items by="id">
38
+ <li class={ done: item.done }>
39
+ <input
40
+ type="checkbox"
41
+ checked=item.done
42
+ checkedChange(v) { items = items.toSpliced(i, 1, { ...item, done: v }) }
43
+ >
44
+ ${item.text}
45
+ </li>
46
+ </for>
47
+ </ul>
48
+ <p>${remaining} left</p>
49
+ </if>
50
+ <else>
51
+ <p>Nothing yet</p>
52
+ </else>
53
+ ```
54
+
55
+ ## Control flow
56
+
57
+ ```marko
58
+ <if=cond> A </if>
59
+ <else if=other> B </else>
60
+ <else> C </else>
61
+
62
+ <for|item, index| of=list by="id"> ${item.name} </for> // by keys the loop (no key= attr!)
63
+ <for|city| of=cities by=(city) => city> ${city} </for> // primitives: by takes a FUNCTION — by=city would be an undefined variable (the loop param is not in scope in by=)
64
+ <for|i| from=0 until=5> ${i} </for> // 0..4
65
+
66
+ <show=open> stays mounted, keeps state (form drafts) when hidden </show>
67
+ ```
68
+
69
+ `<if>` destroys/rebuilds its content; `<show>` just hides it (use for toggles that must keep state).
70
+
71
+ ## Async (`<await>`)
72
+
73
+ ```marko
74
+ import { getUser } from "../data.js";
75
+
76
+ <try>
77
+ <await|user|=getUser()>
78
+ <h2>${user.name}</h2>
79
+ </await>
80
+
81
+ <@placeholder>Loading...</@placeholder>
82
+ <@catch|err|>${err.message}</@catch>
83
+ </try>
84
+ ```
85
+
86
+ `@placeholder`/`@catch` go on `<try>`, never on `<await>`. On the server this streams (placeholder flushes first, content follows). It works in the browser too: hand `<await>` a new promise (e.g. a `<const>` derived from state) and it shows the placeholder again, then the new result.
87
+
88
+ Don't fetch while rendering: start data loads early, pass the PROMISE through the template, and `<await>` it where the data is rendered. Fetching inside each component that renders the data serializes the requests (waterfalls). Under @marko/run, load in the route handler — `return next({ user: getUser() })`, no await — and render with `<await|user|=$global.data.user>`.
89
+
90
+ ## Components
91
+
92
+ - File `src/tags/product-card.marko` is auto-discovered as `<product-card>` from any template (no import needed). Attributes arrive as `input`: `${input.title}`.
93
+ - Body content passed by the parent renders with `<${input.content}/>`.
94
+ - Named sections use attribute tags:
95
+
96
+ ```marko
97
+ /* parent */
98
+ <my-card>
99
+ <@header>Hello</@header>
100
+ body text
101
+ </my-card>
102
+
103
+ /* src/tags/my-card.marko */
104
+ <div class="card">
105
+ <header><${input.header.content}/></header>
106
+ <${input.content}/>
107
+ </div>
108
+ ```
109
+
110
+ - Repeated attr tags (many `<@tab ...>`) arrive as the SINGULAR prop `input.tab`, which is iterable but NOT an array: `input.tab[i]` and `input.tab.length` are undefined. To index or count, spread first: `<const/tabs=[...input.tab ?? []]>` then `tabs[active]`/`tabs.length`. Looping directly is fine: `<for|tab| of=input.tab>`.
111
+ - Conditional attrs: `false`/`null` attrs are omitted from HTML. `aria-selected` etc. want strings: `aria-selected=(i === active && "true")`.
112
+ - `class=` / `style=` accept strings, objects, arrays: `class=["btn", { active }]`, `style={ color }` (single braces).
113
+
114
+ ## Client-side effects (rare — prefer state/const)
115
+
116
+ ```marko
117
+ <div/el/>
118
+ <script>
119
+ // Browser-only. Runs after mount and re-runs when referenced state changes.
120
+ el().focus(); // element refs are getter FUNCTIONS
121
+ const id = setInterval(tick, 1000);
122
+ $signal.onabort = () => clearInterval(id); // cleanup
123
+ </script>
124
+ ```
125
+
126
+ `<style>` = real CSS, extracted & global. `<script>` = reactive effect, NOT an HTML script tag.
127
+
128
+ ## DON'T (these are errors or silently wrong)
129
+
130
+ | Wrong (React/Vue/Marko5 habit) | Right |
131
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------ |
132
+ | `{expr}` in markup, `className`, `key=`, `style={{...}}` | `${expr}`, `class`, `by=` on `<for>`, `style={...}` |
133
+ | `onClick={() => ...}` / `@click` / `on-click("name")` | `onClick() { ... }` |
134
+ | `const [x, setX] = useState()` / `state` / `class {}` block | `<let/x=0>` then `x = 1` |
135
+ | `$ const y = x * 2;` (scriptlets are removed) | `<const/y=x * 2>` |
136
+ | `<let x=0>` | `<let/x=0>` |
137
+ | `<if(cond)>` | `<if=cond>` |
138
+ | `items.push(x)` | `items = items.concat(x)` |
139
+ | `input.renderBody` | `input.content` |
140
+ | `<await>` with `@placeholder`/`@catch` | wrap in `<try>` |
141
+ | `el.focus()` on a ref | `el().focus()` inside `<script>`/handler |
142
+ | `input.tab[0]` / `input.tab.length` | `[...input.tab ?? []]` first (attr tags are iterables, not arrays) |
143
+ | bare text on its own line at template root | wrap in an element (`<p>...`), or prefix the line with `-- ` |
144
+ | `by=item` using the loop variable | `by="propName"` or `by=(item) => key` — `by=` is evaluated outside the loop |
145
+ | `onInput(e) { q = e.target.value }` to sync an input | `value:=q` — the change handler owns the value |
146
+ | fetching inside the component that renders the data | start the promise early (route handler / top of template), pass it down to `<await>` |
@@ -8,7 +8,6 @@ export declare enum AccessorPrefix {
8
8
  ControlledValue = "G",
9
9
  DynamicHTMLLastChild = "H",
10
10
  EventAttributes = "I",
11
- Getter = "J",
12
11
  KeyedScopes = "O",
13
12
  Lifecycle = "K",
14
13
  Promise = "L",
@@ -8,7 +8,6 @@ export declare enum AccessorPrefix {
8
8
  ControlledValue = "ControlledValue:",
9
9
  DynamicHTMLLastChild = "DynamicHTMLLastChild:",
10
10
  EventAttributes = "EventAttributes:",
11
- Getter = "Getter:",
12
11
  KeyedScopes = "KeyedScopes:",
13
12
  Lifecycle = "Lifecycle:",
14
13
  Promise = "Promise:",
package/dist/debug/dom.js CHANGED
@@ -1974,6 +1974,7 @@ const RENDER_BODY_ID = "$compat_renderBody";
1974
1974
  //#endregion
1975
1975
  //#region src/dom/compat.ts
1976
1976
  const classIdToBranch = /* @__PURE__ */ new Map();
1977
+ let classEventResolver;
1977
1978
  const scopesByRender = /* @__PURE__ */ new WeakMap();
1978
1979
  const getRenderScopes = ($global) => {
1979
1980
  const render = self[$global.runtimeId]?.[$global.renderId];
@@ -1988,9 +1989,16 @@ const compat = {
1988
1989
  _resume(SET_SCOPE_REGISTER_ID, (scope) => {
1989
1990
  getRenderScopes(scope["$global"])[scope["#Id"]] = scope;
1990
1991
  if (scope.m5c) classIdToBranch.set(scope.m5c, scope);
1992
+ if (classEventResolver) for (const key in scope) {
1993
+ const resolved = classEventResolver(scope[key], scope);
1994
+ if (resolved !== scope[key]) scope[key] = resolved;
1995
+ }
1991
1996
  });
1992
1997
  _resume(RENDER_BODY_ID, warp10Noop);
1993
1998
  },
1999
+ setClassEventResolver(fn) {
2000
+ classEventResolver = fn;
2001
+ },
1994
2002
  getScope($global, scopeId) {
1995
2003
  return getRenderScopes($global)?.[scopeId];
1996
2004
  },
@@ -1972,6 +1972,7 @@ const RENDER_BODY_ID = "$compat_renderBody";
1972
1972
  //#endregion
1973
1973
  //#region src/dom/compat.ts
1974
1974
  const classIdToBranch = /* @__PURE__ */ new Map();
1975
+ let classEventResolver;
1975
1976
  const scopesByRender = /* @__PURE__ */ new WeakMap();
1976
1977
  const getRenderScopes = ($global) => {
1977
1978
  const render = self[$global.runtimeId]?.[$global.renderId];
@@ -1986,9 +1987,16 @@ const compat = {
1986
1987
  _resume(SET_SCOPE_REGISTER_ID, (scope) => {
1987
1988
  getRenderScopes(scope["$global"])[scope["#Id"]] = scope;
1988
1989
  if (scope.m5c) classIdToBranch.set(scope.m5c, scope);
1990
+ if (classEventResolver) for (const key in scope) {
1991
+ const resolved = classEventResolver(scope[key], scope);
1992
+ if (resolved !== scope[key]) scope[key] = resolved;
1993
+ }
1989
1994
  });
1990
1995
  _resume(RENDER_BODY_ID, warp10Noop);
1991
1996
  },
1997
+ setClassEventResolver(fn) {
1998
+ classEventResolver = fn;
1999
+ },
1992
2000
  getScope($global, scopeId) {
1993
2001
  return getRenderScopes($global)?.[scopeId];
1994
2002
  },
@@ -247,6 +247,8 @@ function _escape_comment(val) {
247
247
  const K_SCOPE_ID = Symbol("Scope ID");
248
248
  const kTouchedIterator = Symbol.for("marko.touchedIterator");
249
249
  const { hasOwnProperty: hasOwnProperty$1 } = {};
250
+ const objectProto = Object.prototype;
251
+ const arrayProto = Array.prototype;
250
252
  const Generator = (function* () {})().constructor;
251
253
  const AsyncGenerator = (async function* () {})().constructor;
252
254
  patchIteratorNext(Generator.prototype);
@@ -685,12 +687,6 @@ function writeProp(state, val, parent, accessor) {
685
687
  }
686
688
  }
687
689
  function writeReferenceOr(state, write, val, parent, accessor) {
688
- const scopeId = val[K_SCOPE_ID];
689
- if (scopeId !== void 0) {
690
- trackScope(state, val, scopeId);
691
- state.buf.push("_(" + scopeId + ")");
692
- return true;
693
- }
694
690
  let ref = state.refs.get(val);
695
691
  if (ref) {
696
692
  if (!trackChannel(state, ref)) {
@@ -811,6 +807,12 @@ function writeNull(state) {
811
807
  }
812
808
  function writeObject(state, val, parent, accessor) {
813
809
  if (val === null) return writeNull(state);
810
+ const scopeId = val[K_SCOPE_ID];
811
+ if (scopeId !== void 0) {
812
+ trackScope(state, val, scopeId);
813
+ state.buf.push("_(" + scopeId + ")");
814
+ return true;
815
+ }
814
816
  const wellKnownObject = KNOWN_OBJECTS.get(val);
815
817
  if (wellKnownObject) {
816
818
  state.buf.push(wellKnownObject);
@@ -819,7 +821,10 @@ function writeObject(state, val, parent, accessor) {
819
821
  return writeReferenceOr(state, writeUnknownObject, val, parent, accessor);
820
822
  }
821
823
  function writeUnknownObject(state, val, ref) {
822
- switch (Object.getPrototypeOf(val)?.constructor) {
824
+ const proto = Object.getPrototypeOf(val);
825
+ if (proto === objectProto) return writePlainObject(state, val, ref);
826
+ if (proto === arrayProto) return writeArray(state, val, ref);
827
+ switch (proto?.constructor) {
823
828
  case void 0: return writeNullObject(state, val, ref);
824
829
  case Object: return writePlainObject(state, val, ref);
825
830
  case Array: return writeArray(state, val, ref);
@@ -863,7 +868,7 @@ function writeUnknownObject(state, val, ref) {
863
868
  }
864
869
  function writePlainObject(state, val, ref) {
865
870
  state.buf.push("{");
866
- writeObjectProps(state, val, ref);
871
+ writeMaybeIterableProps(state, val, ref);
867
872
  state.buf.push("}");
868
873
  return true;
869
874
  }
@@ -1212,7 +1217,7 @@ function writeAsyncGenerator(state, iter, ref) {
1212
1217
  }
1213
1218
  function writeNullObject(state, val, ref) {
1214
1219
  state.buf.push("{");
1215
- state.buf.push(writeObjectProps(state, val, ref) + "__proto__:null}");
1220
+ state.buf.push(writeMaybeIterableProps(state, val, ref) + "__proto__:null}");
1216
1221
  return true;
1217
1222
  }
1218
1223
  function writeObjectProps(state, val, ref) {
@@ -1223,6 +1228,10 @@ function writeObjectProps(state, val, ref) {
1223
1228
  if (writeProp(state, val[key], ref, escapedKey)) sep = ",";
1224
1229
  else state.buf.pop();
1225
1230
  }
1231
+ return sep;
1232
+ }
1233
+ function writeMaybeIterableProps(state, val, ref) {
1234
+ let sep = writeObjectProps(state, val, ref);
1226
1235
  if (hasSymbolIterator(val)) {
1227
1236
  let yieldSelf = "";
1228
1237
  const iterArr = [];
@@ -1309,14 +1318,19 @@ function isCircular(parent, ref) {
1309
1318
  function toObjectKey(name) {
1310
1319
  if (name === "") return "\"\"";
1311
1320
  if (name === "__proto__") return "[\"__proto__\"]";
1312
- const startChar = name[0];
1313
- if (isDigit(startChar)) {
1314
- if (startChar === "0") {
1315
- if (name !== "0") return quote(name, 1);
1316
- } else for (let i = 1; i < name.length; i++) if (!isDigit(name[i])) return quote(name, i);
1317
- } else if (isWord(startChar)) {
1318
- for (let i = 1; i < name.length; i++) if (!isWordOrDigit(name[i])) return quote(name, i);
1319
- } else return quote(name, 0);
1321
+ const len = name.length;
1322
+ const c0 = name.charCodeAt(0);
1323
+ if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
1324
+ if (len !== 1) return quote(name, 1);
1325
+ } else for (let i = 1; i < len; i++) {
1326
+ const c = name.charCodeAt(i);
1327
+ if (c < 48 || c > 57) return quote(name, i);
1328
+ }
1329
+ else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
1330
+ const c = name.charCodeAt(i);
1331
+ if (!(c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 95 || c === 36)) return quote(name, i);
1332
+ }
1333
+ else return quote(name, 0);
1320
1334
  return name;
1321
1335
  }
1322
1336
  function toAccess(accessor) {
@@ -1459,15 +1473,6 @@ function hasOnlyZeros(typedArray) {
1459
1473
  for (let i = 0; i < typedArray.length; i++) if (typedArray[i] !== 0) return false;
1460
1474
  return true;
1461
1475
  }
1462
- function isWordOrDigit(char) {
1463
- return isWord(char) || isDigit(char);
1464
- }
1465
- function isDigit(char) {
1466
- return char >= "0" && char <= "9";
1467
- }
1468
- function isWord(char) {
1469
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char === "_" || char === "$";
1470
- }
1471
1476
  function patchIteratorNext(proto) {
1472
1477
  if (proto.next[kTouchedIterator]) return;
1473
1478
  const { next } = proto;
@@ -2900,6 +2905,7 @@ var ServerRendered = class {
2900
2905
  pipe(stream) {
2901
2906
  this.#read((html) => {
2902
2907
  stream.write(html);
2908
+ stream.flush?.();
2903
2909
  }, (err) => {
2904
2910
  const socket = "socket" in stream && stream.socket;
2905
2911
  if (socket && typeof socket.destroySoon === "function") socket.destroySoon();
@@ -2910,10 +2916,13 @@ var ServerRendered = class {
2910
2916
  }
2911
2917
  toReadable() {
2912
2918
  let cancelled = false;
2919
+ let started = false;
2913
2920
  let boundary;
2914
2921
  const encoder = new TextEncoder();
2915
2922
  return new ReadableStream({
2916
- start: (ctrl) => {
2923
+ pull: (ctrl) => {
2924
+ if (started) return;
2925
+ started = true;
2917
2926
  boundary = this.#read((html) => {
2918
2927
  ctrl.enqueue(encoder.encode(html));
2919
2928
  }, (err) => {
@@ -2928,7 +2937,7 @@ var ServerRendered = class {
2928
2937
  cancelled = true;
2929
2938
  boundary?.abort(reason);
2930
2939
  }
2931
- });
2940
+ }, { highWaterMark: 0 });
2932
2941
  }
2933
2942
  then(onfulfilled, onrejected) {
2934
2943
  return this.#promise().then(onfulfilled, onrejected);
@@ -3153,7 +3162,7 @@ const compat = {
3153
3162
  if (boundary.flush() === 1) throw new Error("Cannot serialize promise across tags/class compat layer.");
3154
3163
  return new Chunk(boundary, null, null, state).flushScript().scripts;
3155
3164
  },
3156
- render(renderer, willRerender, classAPIOut, component, input, completeChunks) {
3165
+ render(renderer, willRerender, classAPIOut, component, input, completeChunks, registerChildScope) {
3157
3166
  const state = this.ensureState(classAPIOut.global);
3158
3167
  const boundary = new Boundary(state);
3159
3168
  let head = new Chunk(boundary, null, null, state);
@@ -3163,7 +3172,7 @@ const compat = {
3163
3172
  for (const key in input) normalizedInput[key === "renderBody" ? "content" : key] = input[key];
3164
3173
  }
3165
3174
  head.render(() => {
3166
- if (willRerender) {
3175
+ if (willRerender || registerChildScope) {
3167
3176
  const scopeId = _peek_scope_id();
3168
3177
  writeScope(scopeId, { m5c: component.id });
3169
3178
  _script(scopeId, SET_SCOPE_REGISTER_ID);
@@ -245,6 +245,8 @@ function _escape_comment(val) {
245
245
  const K_SCOPE_ID = Symbol("Scope ID");
246
246
  const kTouchedIterator = Symbol.for("marko.touchedIterator");
247
247
  const { hasOwnProperty: hasOwnProperty$1 } = {};
248
+ const objectProto = Object.prototype;
249
+ const arrayProto = Array.prototype;
248
250
  const Generator = (function* () {})().constructor;
249
251
  const AsyncGenerator = (async function* () {})().constructor;
250
252
  patchIteratorNext(Generator.prototype);
@@ -683,12 +685,6 @@ function writeProp(state, val, parent, accessor) {
683
685
  }
684
686
  }
685
687
  function writeReferenceOr(state, write, val, parent, accessor) {
686
- const scopeId = val[K_SCOPE_ID];
687
- if (scopeId !== void 0) {
688
- trackScope(state, val, scopeId);
689
- state.buf.push("_(" + scopeId + ")");
690
- return true;
691
- }
692
688
  let ref = state.refs.get(val);
693
689
  if (ref) {
694
690
  if (!trackChannel(state, ref)) {
@@ -809,6 +805,12 @@ function writeNull(state) {
809
805
  }
810
806
  function writeObject(state, val, parent, accessor) {
811
807
  if (val === null) return writeNull(state);
808
+ const scopeId = val[K_SCOPE_ID];
809
+ if (scopeId !== void 0) {
810
+ trackScope(state, val, scopeId);
811
+ state.buf.push("_(" + scopeId + ")");
812
+ return true;
813
+ }
812
814
  const wellKnownObject = KNOWN_OBJECTS.get(val);
813
815
  if (wellKnownObject) {
814
816
  state.buf.push(wellKnownObject);
@@ -817,7 +819,10 @@ function writeObject(state, val, parent, accessor) {
817
819
  return writeReferenceOr(state, writeUnknownObject, val, parent, accessor);
818
820
  }
819
821
  function writeUnknownObject(state, val, ref) {
820
- switch (Object.getPrototypeOf(val)?.constructor) {
822
+ const proto = Object.getPrototypeOf(val);
823
+ if (proto === objectProto) return writePlainObject(state, val, ref);
824
+ if (proto === arrayProto) return writeArray(state, val, ref);
825
+ switch (proto?.constructor) {
821
826
  case void 0: return writeNullObject(state, val, ref);
822
827
  case Object: return writePlainObject(state, val, ref);
823
828
  case Array: return writeArray(state, val, ref);
@@ -861,7 +866,7 @@ function writeUnknownObject(state, val, ref) {
861
866
  }
862
867
  function writePlainObject(state, val, ref) {
863
868
  state.buf.push("{");
864
- writeObjectProps(state, val, ref);
869
+ writeMaybeIterableProps(state, val, ref);
865
870
  state.buf.push("}");
866
871
  return true;
867
872
  }
@@ -1210,7 +1215,7 @@ function writeAsyncGenerator(state, iter, ref) {
1210
1215
  }
1211
1216
  function writeNullObject(state, val, ref) {
1212
1217
  state.buf.push("{");
1213
- state.buf.push(writeObjectProps(state, val, ref) + "__proto__:null}");
1218
+ state.buf.push(writeMaybeIterableProps(state, val, ref) + "__proto__:null}");
1214
1219
  return true;
1215
1220
  }
1216
1221
  function writeObjectProps(state, val, ref) {
@@ -1221,6 +1226,10 @@ function writeObjectProps(state, val, ref) {
1221
1226
  if (writeProp(state, val[key], ref, escapedKey)) sep = ",";
1222
1227
  else state.buf.pop();
1223
1228
  }
1229
+ return sep;
1230
+ }
1231
+ function writeMaybeIterableProps(state, val, ref) {
1232
+ let sep = writeObjectProps(state, val, ref);
1224
1233
  if (hasSymbolIterator(val)) {
1225
1234
  let yieldSelf = "";
1226
1235
  const iterArr = [];
@@ -1307,14 +1316,19 @@ function isCircular(parent, ref) {
1307
1316
  function toObjectKey(name) {
1308
1317
  if (name === "") return "\"\"";
1309
1318
  if (name === "__proto__") return "[\"__proto__\"]";
1310
- const startChar = name[0];
1311
- if (isDigit(startChar)) {
1312
- if (startChar === "0") {
1313
- if (name !== "0") return quote(name, 1);
1314
- } else for (let i = 1; i < name.length; i++) if (!isDigit(name[i])) return quote(name, i);
1315
- } else if (isWord(startChar)) {
1316
- for (let i = 1; i < name.length; i++) if (!isWordOrDigit(name[i])) return quote(name, i);
1317
- } else return quote(name, 0);
1319
+ const len = name.length;
1320
+ const c0 = name.charCodeAt(0);
1321
+ if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
1322
+ if (len !== 1) return quote(name, 1);
1323
+ } else for (let i = 1; i < len; i++) {
1324
+ const c = name.charCodeAt(i);
1325
+ if (c < 48 || c > 57) return quote(name, i);
1326
+ }
1327
+ else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
1328
+ const c = name.charCodeAt(i);
1329
+ if (!(c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 95 || c === 36)) return quote(name, i);
1330
+ }
1331
+ else return quote(name, 0);
1318
1332
  return name;
1319
1333
  }
1320
1334
  function toAccess(accessor) {
@@ -1457,15 +1471,6 @@ function hasOnlyZeros(typedArray) {
1457
1471
  for (let i = 0; i < typedArray.length; i++) if (typedArray[i] !== 0) return false;
1458
1472
  return true;
1459
1473
  }
1460
- function isWordOrDigit(char) {
1461
- return isWord(char) || isDigit(char);
1462
- }
1463
- function isDigit(char) {
1464
- return char >= "0" && char <= "9";
1465
- }
1466
- function isWord(char) {
1467
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char === "_" || char === "$";
1468
- }
1469
1474
  function patchIteratorNext(proto) {
1470
1475
  if (proto.next[kTouchedIterator]) return;
1471
1476
  const { next } = proto;
@@ -2898,6 +2903,7 @@ var ServerRendered = class {
2898
2903
  pipe(stream) {
2899
2904
  this.#read((html) => {
2900
2905
  stream.write(html);
2906
+ stream.flush?.();
2901
2907
  }, (err) => {
2902
2908
  const socket = "socket" in stream && stream.socket;
2903
2909
  if (socket && typeof socket.destroySoon === "function") socket.destroySoon();
@@ -2908,10 +2914,13 @@ var ServerRendered = class {
2908
2914
  }
2909
2915
  toReadable() {
2910
2916
  let cancelled = false;
2917
+ let started = false;
2911
2918
  let boundary;
2912
2919
  const encoder = new TextEncoder();
2913
2920
  return new ReadableStream({
2914
- start: (ctrl) => {
2921
+ pull: (ctrl) => {
2922
+ if (started) return;
2923
+ started = true;
2915
2924
  boundary = this.#read((html) => {
2916
2925
  ctrl.enqueue(encoder.encode(html));
2917
2926
  }, (err) => {
@@ -2926,7 +2935,7 @@ var ServerRendered = class {
2926
2935
  cancelled = true;
2927
2936
  boundary?.abort(reason);
2928
2937
  }
2929
- });
2938
+ }, { highWaterMark: 0 });
2930
2939
  }
2931
2940
  then(onfulfilled, onrejected) {
2932
2941
  return this.#promise().then(onfulfilled, onrejected);
@@ -3151,7 +3160,7 @@ const compat = {
3151
3160
  if (boundary.flush() === 1) throw new Error("Cannot serialize promise across tags/class compat layer.");
3152
3161
  return new Chunk(boundary, null, null, state).flushScript().scripts;
3153
3162
  },
3154
- render(renderer, willRerender, classAPIOut, component, input, completeChunks) {
3163
+ render(renderer, willRerender, classAPIOut, component, input, completeChunks, registerChildScope) {
3155
3164
  const state = this.ensureState(classAPIOut.global);
3156
3165
  const boundary = new Boundary(state);
3157
3166
  let head = new Chunk(boundary, null, null, state);
@@ -3161,7 +3170,7 @@ const compat = {
3161
3170
  for (const key in input) normalizedInput[key === "renderBody" ? "content" : key] = input[key];
3162
3171
  }
3163
3172
  head.render(() => {
3164
- if (willRerender) {
3173
+ if (willRerender || registerChildScope) {
3165
3174
  const scopeId = _peek_scope_id();
3166
3175
  writeScope(scopeId, { m5c: component.id });
3167
3176
  _script(scopeId, SET_SCOPE_REGISTER_ID);
@@ -6,6 +6,7 @@ export declare const compat: {
6
6
  patchDynamicTag: typeof patchDynamicTag;
7
7
  queueEffect: typeof queueEffect;
8
8
  init(warp10Noop: any): void;
9
+ setClassEventResolver(fn: (value: unknown, scope: Scope) => unknown): void;
9
10
  getScope($global: Record<string, unknown>, scopeId: unknown): Scope | undefined;
10
11
  setRendererId(renderer: any, id: unknown): void;
11
12
  isRenderer(renderer: any): any;
package/dist/dom.js CHANGED
@@ -51,7 +51,7 @@ let empty = [], rest = Symbol(), toDelimitedString = function toDelimitedString(
51
51
  typeof by == "string" ? forOf(all, (item, i) => cb(item[by], [item, i])) : forOf(all, (item, i) => cb(by(item, i), [item, i]));
52
52
  }), _for_in = /* @__PURE__ */ loop(([obj, by = byFirstArg], cb) => forIn(obj, (key, value) => cb(by(key, value), [key, value]))), _for_to = /* @__PURE__ */ loop(([to, from, step, by = byFirstArg], cb) => forTo(to, from, step, (v) => cb(by(v), [v]))), _for_until = /* @__PURE__ */ loop(([until, from, step, by = byFirstArg], cb) => forUntil(until, from, step, (v) => cb(by(v), [v]))), rendering, runId = 2, caughtError = /* @__PURE__ */ new WeakSet(), placeholderShown = /* @__PURE__ */ new WeakSet(), pendingEffects = [], pendingRenders = [], runEffects = ((effects) => {
53
53
  for (let i = 0; i < effects.length;) effects[i++](effects[i++]);
54
- }), runRender = (render) => render.c(render.b, render.d), catchEnabled, classIdToBranch = /* @__PURE__ */ new Map(), scopesByRender = /* @__PURE__ */ new WeakMap(), getRenderScopes = ($global) => {
54
+ }), runRender = (render) => render.c(render.b, render.d), catchEnabled, classIdToBranch = /* @__PURE__ */ new Map(), classEventResolver, scopesByRender = /* @__PURE__ */ new WeakMap(), getRenderScopes = ($global) => {
55
55
  let render = self[$global.runtimeId]?.[$global.renderId], scopes = render && scopesByRender.get(render);
56
56
  return render && !scopes && scopesByRender.set(render, scopes = {}), scopes;
57
57
  }, compat = {
@@ -59,9 +59,15 @@ let empty = [], rest = Symbol(), toDelimitedString = function toDelimitedString(
59
59
  queueEffect,
60
60
  init(warp10Noop) {
61
61
  _resume("$C_s", (scope) => {
62
- getRenderScopes(scope.$)[scope.L] = scope, scope.m5c && classIdToBranch.set(scope.m5c, scope);
62
+ if (getRenderScopes(scope.$)[scope.L] = scope, scope.m5c && classIdToBranch.set(scope.m5c, scope), classEventResolver) for (let key in scope) {
63
+ let resolved = classEventResolver(scope[key], scope);
64
+ resolved !== scope[key] && (scope[key] = resolved);
65
+ }
63
66
  }), _resume("$C_b", warp10Noop);
64
67
  },
68
+ setClassEventResolver(fn) {
69
+ classEventResolver = fn;
70
+ },
65
71
  getScope($global, scopeId) {
66
72
  return getRenderScopes($global)?.[scopeId];
67
73
  },
package/dist/dom.mjs CHANGED
@@ -51,7 +51,7 @@ let empty = [], rest = Symbol(), toDelimitedString = function toDelimitedString(
51
51
  typeof by == "string" ? forOf(all, (item, i) => cb(item[by], [item, i])) : forOf(all, (item, i) => cb(by(item, i), [item, i]));
52
52
  }), _for_in = /* @__PURE__ */ loop(([obj, by = byFirstArg], cb) => forIn(obj, (key, value) => cb(by(key, value), [key, value]))), _for_to = /* @__PURE__ */ loop(([to, from, step, by = byFirstArg], cb) => forTo(to, from, step, (v) => cb(by(v), [v]))), _for_until = /* @__PURE__ */ loop(([until, from, step, by = byFirstArg], cb) => forUntil(until, from, step, (v) => cb(by(v), [v]))), rendering, runId = 2, caughtError = /* @__PURE__ */ new WeakSet(), placeholderShown = /* @__PURE__ */ new WeakSet(), pendingEffects = [], pendingRenders = [], runEffects = ((effects) => {
53
53
  for (let i = 0; i < effects.length;) effects[i++](effects[i++]);
54
- }), runRender = (render) => render.c(render.b, render.d), catchEnabled, classIdToBranch = /* @__PURE__ */ new Map(), scopesByRender = /* @__PURE__ */ new WeakMap(), getRenderScopes = ($global) => {
54
+ }), runRender = (render) => render.c(render.b, render.d), catchEnabled, classIdToBranch = /* @__PURE__ */ new Map(), classEventResolver, scopesByRender = /* @__PURE__ */ new WeakMap(), getRenderScopes = ($global) => {
55
55
  let render = self[$global.runtimeId]?.[$global.renderId], scopes = render && scopesByRender.get(render);
56
56
  return render && !scopes && scopesByRender.set(render, scopes = {}), scopes;
57
57
  }, compat = {
@@ -59,9 +59,15 @@ let empty = [], rest = Symbol(), toDelimitedString = function toDelimitedString(
59
59
  queueEffect,
60
60
  init(warp10Noop) {
61
61
  _resume("$C_s", (scope) => {
62
- getRenderScopes(scope.$)[scope.L] = scope, scope.m5c && classIdToBranch.set(scope.m5c, scope);
62
+ if (getRenderScopes(scope.$)[scope.L] = scope, scope.m5c && classIdToBranch.set(scope.m5c, scope), classEventResolver) for (let key in scope) {
63
+ let resolved = classEventResolver(scope[key], scope);
64
+ resolved !== scope[key] && (scope[key] = resolved);
65
+ }
63
66
  }), _resume("$C_b", warp10Noop);
64
67
  },
68
+ setClassEventResolver(fn) {
69
+ classEventResolver = fn;
70
+ },
65
71
  getScope($global, scopeId) {
66
72
  return getRenderScopes($global)?.[scopeId];
67
73
  },
@@ -16,7 +16,7 @@ export declare const compat: {
16
16
  writeSetScopeForComponent(branchId: number, m5c: string, m5i: unknown): void;
17
17
  toJSON(): (this: WeakKey) => [registryId: string, scopeId: unknown] | undefined;
18
18
  flushScript($global: any): string;
19
- render(renderer: ServerRenderer, willRerender: boolean, classAPIOut: any, component: any, input: any, completeChunks: Chunk[]): void;
19
+ render(renderer: ServerRenderer, willRerender: boolean, classAPIOut: any, component: any, input: any, completeChunks: Chunk[], registerChildScope?: boolean): void;
20
20
  register: typeof register;
21
21
  registerRenderBody(fn: any): void;
22
22
  };