@marko/runtime-tags 6.3.5 → 6.3.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/cheatsheet.md CHANGED
@@ -14,7 +14,8 @@ Marko 6 = HTML superset. NOT JSX, NOT old Marko 4/5. `.marko` files are componen
14
14
  - object: `user = { ...user, name }`
15
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
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 }>`.
17
+ 7. Transform in the handler when needed — number inputs give STRINGS: `<input type="number" value=n valueChange(v) { n = +v }>`, or `value:parseFloat:=n`.
18
+ 8. Radio/checkbox groups: `checkedValue:=picked` on each input (shared var, distinct `value=`) — the match is checked; array var for multi-checkbox. Dropdown: `<select value:=picked>`.
18
19
 
19
20
  ## Canonical component (copy this shape)
20
21
 
@@ -109,7 +110,8 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
109
110
 
110
111
  - 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
112
  - 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
+ - `class=` / `style=` accept strings, objects, arrays: `class=["btn", { active }]`, `style={ color }` (single braces). `style=` keys are kebab-case CSS names (`{ "background-color": c }`), not camelCase.
114
+ - `<id/x>` mints a collision-free id for label/input wiring (`<label for=x>`/`<input id=x>`) — don't hardcode ids in reusable tags; `<id/x=input.id>` reuses a caller's.
113
115
 
114
116
  ## Client-side effects (rare — prefer state/const)
115
117
 
@@ -125,6 +127,26 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
125
127
 
126
128
  `<style>` = real CSS, extracted & global. `<script>` = reactive effect, NOT an HTML script tag.
127
129
 
130
+ Imperative libs (charts, maps) needing mount/update/destroy: use `<lifecycle>`, not a hand-wired `<script>`. `this` persists across all three; return an object from `onMount` to stash the instance:
131
+
132
+ ```marko
133
+ <canvas/canvas/>
134
+ <lifecycle
135
+ onMount() { return { chart: new Chart(canvas(), data) } }
136
+ onUpdate() { this.chart.setData(data) } // re-runs when `data` changes
137
+ onDestroy() { this.chart.destroy() }
138
+ />
139
+ ```
140
+
141
+ ## Lazy loading
142
+
143
+ Defer a tag's JS into its own bundle until a trigger fires: `render`, `visible#sel`, `idle`, `media(...)`, `on-click#sel` (combine with `|`). Server HTML renders immediately; `<try>` shows a `@placeholder` while loading. Don't hand-roll an `IntersectionObserver`.
144
+
145
+ ```marko
146
+ import PriceChart from "<price-chart>" with { load: "visible#chart" }
147
+ <div#chart><PriceChart symbol=input.symbol/></div>
148
+ ```
149
+
128
150
  ## DON'T (these are errors or silently wrong)
129
151
 
130
152
  | Wrong (React/Vue/Marko5 habit) | Right |
@@ -144,3 +166,9 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
144
166
  | `by=item` using the loop variable | `by="propName"` or `by=(item) => key` — `by=` is evaluated outside the loop |
145
167
  | `onInput(e) { q = e.target.value }` to sync an input | `value:=q` — the change handler owns the value |
146
168
  | fetching inside the component that renders the data | start the promise early (route handler / top of template), pass it down to `<await>` |
169
+ | `style={ backgroundColor: c }` (camelCase keys) | `style={ "background-color": c }` (kebab-case) |
170
+ | `this.querySelector` / `this.getRootNode()` in `<script>` | element ref getter: `<div/el>` then `el()` (there is no `this`) |
171
+ | `<div/my-el>` / `<input/card-input>` (hyphen in tag var) | valid JS identifier: `<div/myEl>` |
172
+ | hand-rolled radios `checked=x checkedChange(v){…}` | `checkedValue:=picked` on each radio (shared var, distinct `value=`) |
173
+ | hand-rolled `IntersectionObserver` to defer a widget's JS | `import W from "<w>" with { load: "visible#sel" }` |
174
+ | imperative lib wired through `<script>` mount + cleanup | `<lifecycle onMount/onUpdate/onDestroy>` (keeps `this` across all three) |
package/dist/debug/dom.js CHANGED
@@ -68,7 +68,12 @@ function _call(fn, v) {
68
68
  function stringifyClassObject(name, value) {
69
69
  return value ? name : "";
70
70
  }
71
+ const warnedStyleKeys = /* @__PURE__ */ new Set();
71
72
  function stringifyStyleObject(name, value) {
73
+ if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
74
+ warnedStyleKeys.add(name);
75
+ console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
76
+ }
72
77
  return value || value === 0 ? name + ":" + value : "";
73
78
  }
74
79
  function escapeStyleValue(str) {
@@ -66,7 +66,12 @@ function _call(fn, v) {
66
66
  function stringifyClassObject(name, value) {
67
67
  return value ? name : "";
68
68
  }
69
+ const warnedStyleKeys = /* @__PURE__ */ new Set();
69
70
  function stringifyStyleObject(name, value) {
71
+ if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
72
+ warnedStyleKeys.add(name);
73
+ console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
74
+ }
70
75
  return value || value === 0 ? name + ":" + value : "";
71
76
  }
72
77
  function escapeStyleValue(str) {
@@ -64,7 +64,12 @@ function getWrongAttrSuggestion(name) {
64
64
  function stringifyClassObject(name, value) {
65
65
  return value ? name : "";
66
66
  }
67
+ const warnedStyleKeys = /* @__PURE__ */ new Set();
67
68
  function stringifyStyleObject(name, value) {
69
+ if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
70
+ warnedStyleKeys.add(name);
71
+ console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
72
+ }
68
73
  return value || value === 0 ? name + ":" + value : "";
69
74
  }
70
75
  function escapeStyleValue(str) {
@@ -1169,25 +1174,39 @@ function writeGenerator(state, iter, ref) {
1169
1174
  state.buf.push("(function*(){}())");
1170
1175
  return true;
1171
1176
  }
1172
- let sep = "";
1173
- state.buf.push("(function*(){");
1177
+ const yields = [];
1178
+ let returnValue;
1179
+ let needsId;
1174
1180
  while (true) {
1175
1181
  const { value, done } = iter.next();
1176
1182
  if (done) {
1177
- if (value !== void 0) {
1178
- state.buf.push(sep + "return ");
1179
- writeProp(state, value, ref, "");
1180
- }
1183
+ returnValue = value;
1181
1184
  break;
1182
1185
  }
1183
- if (value === void 0) state.buf.push(sep + "yield");
1184
- else {
1185
- state.buf.push(sep + "yield ");
1186
- writeProp(state, value, ref, "");
1186
+ needsId ||= isDedupedMember(value);
1187
+ yields.push(value);
1188
+ }
1189
+ if (returnValue === void 0 && !yields.length) {
1190
+ state.buf.push("(function*(){})()");
1191
+ return true;
1192
+ }
1193
+ state.buf.push(returnValue === void 0 ? "(function*(a){yield*a})(" : "(function*(a,r){yield*a;return r})(");
1194
+ if (needsId) {
1195
+ const arrayRef = new Reference(ref, null, state.flush, null, nextRefAccess(state));
1196
+ state.buf.push(arrayRef.id + "=");
1197
+ writeArray(state, yields, arrayRef);
1198
+ } else writeArray(state, yields, new Reference(ref, null, state.flush, state.buf.length));
1199
+ if (returnValue !== void 0) {
1200
+ const sepIndex = state.buf.push(",") - 1;
1201
+ if (writeProp(state, returnValue, ref, "") && isDedupedMember(returnValue)) {
1202
+ const retRef = typeof returnValue === "string" ? state.strs.get(returnValue) : state.refs.get(returnValue);
1203
+ if (retRef && !retRef.id && retRef.scopeId === void 0) {
1204
+ retRef.id = nextRefAccess(state);
1205
+ state.buf[sepIndex] = "," + retRef.id + "=";
1206
+ }
1187
1207
  }
1188
- sep = ";";
1189
1208
  }
1190
- state.buf.push("})()");
1209
+ state.buf.push(")");
1191
1210
  return true;
1192
1211
  }
1193
1212
  function writeAsyncGenerator(state, iter, ref) {
@@ -1322,9 +1341,12 @@ function toObjectKey(name) {
1322
1341
  const c0 = name.charCodeAt(0);
1323
1342
  if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
1324
1343
  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);
1344
+ } else {
1345
+ for (let i = 1; i < len; i++) {
1346
+ const c = name.charCodeAt(i);
1347
+ if (c < 48 || c > 57) return quote(name, i);
1348
+ }
1349
+ if (len > 15 && "" + +name !== name) return quote(name, 0);
1328
1350
  }
1329
1351
  else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
1330
1352
  const c = name.charCodeAt(i);
@@ -62,7 +62,12 @@ function getWrongAttrSuggestion(name) {
62
62
  function stringifyClassObject(name, value) {
63
63
  return value ? name : "";
64
64
  }
65
+ const warnedStyleKeys = /* @__PURE__ */ new Set();
65
66
  function stringifyStyleObject(name, value) {
67
+ if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
68
+ warnedStyleKeys.add(name);
69
+ console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
70
+ }
66
71
  return value || value === 0 ? name + ":" + value : "";
67
72
  }
68
73
  function escapeStyleValue(str) {
@@ -1167,25 +1172,39 @@ function writeGenerator(state, iter, ref) {
1167
1172
  state.buf.push("(function*(){}())");
1168
1173
  return true;
1169
1174
  }
1170
- let sep = "";
1171
- state.buf.push("(function*(){");
1175
+ const yields = [];
1176
+ let returnValue;
1177
+ let needsId;
1172
1178
  while (true) {
1173
1179
  const { value, done } = iter.next();
1174
1180
  if (done) {
1175
- if (value !== void 0) {
1176
- state.buf.push(sep + "return ");
1177
- writeProp(state, value, ref, "");
1178
- }
1181
+ returnValue = value;
1179
1182
  break;
1180
1183
  }
1181
- if (value === void 0) state.buf.push(sep + "yield");
1182
- else {
1183
- state.buf.push(sep + "yield ");
1184
- writeProp(state, value, ref, "");
1184
+ needsId ||= isDedupedMember(value);
1185
+ yields.push(value);
1186
+ }
1187
+ if (returnValue === void 0 && !yields.length) {
1188
+ state.buf.push("(function*(){})()");
1189
+ return true;
1190
+ }
1191
+ state.buf.push(returnValue === void 0 ? "(function*(a){yield*a})(" : "(function*(a,r){yield*a;return r})(");
1192
+ if (needsId) {
1193
+ const arrayRef = new Reference(ref, null, state.flush, null, nextRefAccess(state));
1194
+ state.buf.push(arrayRef.id + "=");
1195
+ writeArray(state, yields, arrayRef);
1196
+ } else writeArray(state, yields, new Reference(ref, null, state.flush, state.buf.length));
1197
+ if (returnValue !== void 0) {
1198
+ const sepIndex = state.buf.push(",") - 1;
1199
+ if (writeProp(state, returnValue, ref, "") && isDedupedMember(returnValue)) {
1200
+ const retRef = typeof returnValue === "string" ? state.strs.get(returnValue) : state.refs.get(returnValue);
1201
+ if (retRef && !retRef.id && retRef.scopeId === void 0) {
1202
+ retRef.id = nextRefAccess(state);
1203
+ state.buf[sepIndex] = "," + retRef.id + "=";
1204
+ }
1185
1205
  }
1186
- sep = ";";
1187
1206
  }
1188
- state.buf.push("})()");
1207
+ state.buf.push(")");
1189
1208
  return true;
1190
1209
  }
1191
1210
  function writeAsyncGenerator(state, iter, ref) {
@@ -1320,9 +1339,12 @@ function toObjectKey(name) {
1320
1339
  const c0 = name.charCodeAt(0);
1321
1340
  if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
1322
1341
  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);
1342
+ } else {
1343
+ for (let i = 1; i < len; i++) {
1344
+ const c = name.charCodeAt(i);
1345
+ if (c < 48 || c > 57) return quote(name, i);
1346
+ }
1347
+ if (len > 15 && "" + +name !== name) return quote(name, 0);
1326
1348
  }
1327
1349
  else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
1328
1350
  const c = name.charCodeAt(i);
package/dist/html.js CHANGED
@@ -823,16 +823,28 @@ function writeReadableStream(state, val, ref) {
823
823
  }
824
824
  function writeGenerator(state, iter, ref) {
825
825
  if (iter[kTouchedIterator]) return state.buf.push("(function*(){}())"), !0;
826
- let sep = "";
827
- for (state.buf.push("(function*(){");;) {
826
+ let yields = [], returnValue, needsId;
827
+ for (;;) {
828
828
  let { value, done } = iter.next();
829
829
  if (done) {
830
- value !== void 0 && (state.buf.push(sep + "return "), writeProp(state, value, ref, ""));
830
+ returnValue = value;
831
831
  break;
832
832
  }
833
- value === void 0 ? state.buf.push(sep + "yield") : (state.buf.push(sep + "yield "), writeProp(state, value, ref, "")), sep = ";";
833
+ needsId ||= isDedupedMember(value), yields.push(value);
834
+ }
835
+ if (returnValue === void 0 && !yields.length) return state.buf.push("(function*(){})()"), !0;
836
+ if (state.buf.push(returnValue === void 0 ? "(function*(a){yield*a})(" : "(function*(a,r){yield*a;return r})("), needsId) {
837
+ let arrayRef = new Reference(ref, null, state.flush, null, nextRefAccess(state));
838
+ state.buf.push(arrayRef.id + "="), writeArray(state, yields, arrayRef);
839
+ } else writeArray(state, yields, new Reference(ref, null, state.flush, state.buf.length));
840
+ if (returnValue !== void 0) {
841
+ let sepIndex = state.buf.push(",") - 1;
842
+ if (writeProp(state, returnValue, ref, "") && isDedupedMember(returnValue)) {
843
+ let retRef = typeof returnValue == "string" ? state.strs.get(returnValue) : state.refs.get(returnValue);
844
+ retRef && !retRef.id && retRef.scopeId === void 0 && (retRef.id = nextRefAccess(state), state.buf[sepIndex] = "," + retRef.id + "=");
845
+ }
834
846
  }
835
- return state.buf.push("})()"), !0;
847
+ return state.buf.push(")"), !0;
836
848
  }
837
849
  function writeAsyncGenerator(state, iter, ref) {
838
850
  if (iter[kTouchedIterator]) return state.buf.push("(async function*(){}())"), !0;
@@ -902,9 +914,12 @@ function toObjectKey(name) {
902
914
  let len = name.length, c0 = name.charCodeAt(0);
903
915
  if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
904
916
  if (len !== 1) return quote(name, 1);
905
- } else for (let i = 1; i < len; i++) {
906
- let c = name.charCodeAt(i);
907
- if (c < 48 || c > 57) return quote(name, i);
917
+ } else {
918
+ for (let i = 1; i < len; i++) {
919
+ let c = name.charCodeAt(i);
920
+ if (c < 48 || c > 57) return quote(name, i);
921
+ }
922
+ if (len > 15 && "" + +name !== name) return quote(name, 0);
908
923
  }
909
924
  else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
910
925
  let c = name.charCodeAt(i);
package/dist/html.mjs CHANGED
@@ -822,16 +822,28 @@ function writeReadableStream(state, val, ref) {
822
822
  }
823
823
  function writeGenerator(state, iter, ref) {
824
824
  if (iter[kTouchedIterator]) return state.buf.push("(function*(){}())"), !0;
825
- let sep = "";
826
- for (state.buf.push("(function*(){");;) {
825
+ let yields = [], returnValue, needsId;
826
+ for (;;) {
827
827
  let { value, done } = iter.next();
828
828
  if (done) {
829
- value !== void 0 && (state.buf.push(sep + "return "), writeProp(state, value, ref, ""));
829
+ returnValue = value;
830
830
  break;
831
831
  }
832
- value === void 0 ? state.buf.push(sep + "yield") : (state.buf.push(sep + "yield "), writeProp(state, value, ref, "")), sep = ";";
832
+ needsId ||= isDedupedMember(value), yields.push(value);
833
+ }
834
+ if (returnValue === void 0 && !yields.length) return state.buf.push("(function*(){})()"), !0;
835
+ if (state.buf.push(returnValue === void 0 ? "(function*(a){yield*a})(" : "(function*(a,r){yield*a;return r})("), needsId) {
836
+ let arrayRef = new Reference(ref, null, state.flush, null, nextRefAccess(state));
837
+ state.buf.push(arrayRef.id + "="), writeArray(state, yields, arrayRef);
838
+ } else writeArray(state, yields, new Reference(ref, null, state.flush, state.buf.length));
839
+ if (returnValue !== void 0) {
840
+ let sepIndex = state.buf.push(",") - 1;
841
+ if (writeProp(state, returnValue, ref, "") && isDedupedMember(returnValue)) {
842
+ let retRef = typeof returnValue == "string" ? state.strs.get(returnValue) : state.refs.get(returnValue);
843
+ retRef && !retRef.id && retRef.scopeId === void 0 && (retRef.id = nextRefAccess(state), state.buf[sepIndex] = "," + retRef.id + "=");
844
+ }
833
845
  }
834
- return state.buf.push("})()"), !0;
846
+ return state.buf.push(")"), !0;
835
847
  }
836
848
  function writeAsyncGenerator(state, iter, ref) {
837
849
  if (iter[kTouchedIterator]) return state.buf.push("(async function*(){}())"), !0;
@@ -901,9 +913,12 @@ function toObjectKey(name) {
901
913
  let len = name.length, c0 = name.charCodeAt(0);
902
914
  if (c0 >= 48 && c0 <= 57) if (c0 === 48) {
903
915
  if (len !== 1) return quote(name, 1);
904
- } else for (let i = 1; i < len; i++) {
905
- let c = name.charCodeAt(i);
906
- if (c < 48 || c > 57) return quote(name, i);
916
+ } else {
917
+ for (let i = 1; i < len; i++) {
918
+ let c = name.charCodeAt(i);
919
+ if (c < 48 || c > 57) return quote(name, i);
920
+ }
921
+ if (len > 15 && "" + +name !== name) return quote(name, 0);
907
922
  }
908
923
  else if (c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90 || c0 === 95 || c0 === 36) for (let i = 1; i < len; i++) {
909
924
  let c = name.charCodeAt(i);
@@ -1,5 +1,6 @@
1
1
  import type { Config } from "@marko/compiler";
2
2
  export { version } from "../../package.json";
3
+ export declare const cheatsheet = "../../cheatsheet.md";
3
4
  export declare const tagDiscoveryDirs: string[];
4
5
  export { default as internalEntryBuilder } from "./util/entry-builder";
5
6
  export declare const preferAPI = "tags";
@@ -3802,6 +3802,8 @@ var for_default = {
3802
3802
  default: throw tag.buildCodeFrameError("The [`<for>` tag](https://markojs.com/docs/reference/core-tag#for) requires an `of=`, `in=`, or `to=` attribute.");
3803
3803
  }
3804
3804
  if (!isAttrTag) allowAttrs.push("by");
3805
+ const keyAttr = tag.node.attributes.find((attr) => attr.type === "MarkoAttribute" && attr.name === "key");
3806
+ if (keyAttr) throw tag.hub.buildError(keyAttr, `The [\`<for>\` tag](https://markojs.com/docs/reference/core-tag#for) keys items with the \`by=\` attribute, not \`key=\`. ${forType === "of" ? "Use `by=\"propName\"` or `by=(item, index) => key`" : forType === "in" ? "Use `by=(key, value) => key`" : "Use `by=(num) => key`"}.`);
3805
3807
  (0, _marko_compiler_babel_utils.assertAllowedAttributes)(tag, allowAttrs);
3806
3808
  if (isAttrTag) return;
3807
3809
  const byAttr = getKnownAttrValues(tag.node).by;
@@ -4359,6 +4361,7 @@ var native_tag_default = {
4359
4361
  seen[attr.name] = attr;
4360
4362
  assertValidNativeAttrName(tag, attr);
4361
4363
  assertNativeAttrValueType(tag, attr);
4364
+ if (attr.name === "style") warnCamelCaseStyleKeys(tag, attr);
4362
4365
  if (injectNonce && attr.name === "nonce") injectNonce = false;
4363
4366
  if (isEventOrChangeHandler(attr.name)) assertNativeHandlerAttr(tag, attr);
4364
4367
  if (isEventHandler(attr.name)) {
@@ -4810,6 +4813,19 @@ function assertNativeAttrValueType(tag, attr) {
4810
4813
  if (_marko_compiler.types.isFunction(attr.value)) throw tag.hub.buildError(attr, `The \`${name}\` attribute cannot be a function.`, Error);
4811
4814
  if (_marko_compiler.types.isObjectExpression(attr.value)) throw tag.hub.buildError(attr, `The \`${name}\` attribute cannot be a plain object (it would render as \`[object Object]\`).`, Error);
4812
4815
  }
4816
+ function warnCamelCaseStyleKeys(tag, attr) {
4817
+ const { value } = attr;
4818
+ const objects = value.type === "ObjectExpression" ? [value] : value.type === "ArrayExpression" ? value.elements.filter((el) => el?.type === "ObjectExpression") : [];
4819
+ for (const object of objects) for (const prop of object.properties) {
4820
+ if (prop.type !== "ObjectProperty" || prop.computed) continue;
4821
+ const { key } = prop;
4822
+ const name = key.type === "Identifier" ? key.name : key.type === "StringLiteral" ? key.value : void 0;
4823
+ if (name && !name.startsWith("--") && !name.includes("-") && /[A-Z]/.test(name)) (0, _marko_compiler_babel_utils.diagnosticWarn)(tag, {
4824
+ label: `\`${name}\` is not a CSS property name; the [\`style=\` object](https://markojs.com/docs/reference/native-tag#style) writes keys out verbatim (unlike the camelCased DOM style API), so this renders as invalid CSS the browser ignores. Use the kebab-case name \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`,
4825
+ loc: key.loc ?? void 0
4826
+ });
4827
+ }
4828
+ }
4813
4829
  function assertNativeHandlerAttr(tag, attr) {
4814
4830
  if (_marko_compiler.types.isObjectExpression(attr.value)) throw tag.hub.buildError(attr.value, `The \`${attr.name}\` ${isEventHandler(attr.name) ? "event handler" : "change handler"} on a [native tag](https://markojs.com/docs/reference/native-tag) must be a function. Attribute values in Marko are plain JavaScript expressions, not JSX; remove the wrapping \`{ }\` (e.g. \`${attr.name}=myHandler\` or \`${attr.name}() { ... }\`).`, Error);
4815
4831
  if ((0, _marko_compiler_babel_utils.computeNode)(attr.value)?.value) throw tag.hub.buildError(attr.value, `The \`${attr.name}\` ${isEventHandler(attr.name) ? "event handler" : "change handler"} on a [native tag](https://markojs.com/docs/reference/native-tag) must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …).`, Error);
@@ -9356,13 +9372,36 @@ function tagNotFoundError(tag) {
9356
9372
  if (tagName && tag.scope.hasBinding(tagName)) return tag.get("name").buildCodeFrameError(`Local variables must be in a [dynamic tag](https://markojs.com/docs/reference/language#dynamic-tags) unless they are PascalCase. Use \`<\${${tagName}}/>\` or rename to \`${tagName.charAt(0).toUpperCase() + tagName.slice(1)}\`.`);
9357
9373
  let didYouMean = "";
9358
9374
  const knownWrongTagHint = tagName && knownWrongTags.get(tagName);
9375
+ const proseText = tagName && getProseText(tag);
9359
9376
  if (knownWrongTagHint) didYouMean = ` ${knownWrongTagHint}`;
9377
+ else if (proseText) didYouMean = ` If this line is meant to be text, prefix it with \`--\` (e.g. \`-- ${proseText}\`) or wrap it in an element such as \`<p>${proseText}</p>\`.`;
9360
9378
  else if (tagName) {
9361
9379
  const closestTag = (0, fastest_levenshtein.closest)(tagName, Object.keys((0, _marko_compiler_babel_utils.getTaglibLookup)(tag.hub.file).merged.tags));
9362
9380
  if ((0, fastest_levenshtein.distance)(tagName, closestTag) < 4) didYouMean = ` Did you mean \`<${closestTag}>\`?`;
9363
9381
  }
9364
9382
  return tag.get("name").buildCodeFrameError(`Unable to find entry point for [custom tag](https://markojs.com/docs/reference/custom-tag#relative-custom-tags) \`<${tagName}>\`.${didYouMean}`);
9365
9383
  }
9384
+ const wordReg = /^[A-Za-z]+$/;
9385
+ function getProseText(tag) {
9386
+ const { node } = tag;
9387
+ const tagName = getTagName(tag);
9388
+ if (!tagName || !wordReg.test(tagName) || node.var || node.body.params.length || node.body.body.length || !node.attributes.length || !isConciseModeTag(node)) return;
9389
+ return collectProseWords(node, tagName);
9390
+ }
9391
+ function isConciseModeTag(node) {
9392
+ const tagLoc = node.loc;
9393
+ const nameLoc = node.name.loc;
9394
+ if (!tagLoc || !nameLoc) return false;
9395
+ return tagLoc.start.line === nameLoc.start.line && tagLoc.start.column === nameLoc.start.column;
9396
+ }
9397
+ function collectProseWords(node, tagName) {
9398
+ let text = tagName;
9399
+ for (const attr of node.attributes) {
9400
+ if (attr.type !== "MarkoAttribute" || attr.default || !wordReg.test(attr.name) || attr.value.type !== "BooleanLiteral" || attr.value.value !== true) return;
9401
+ text += " " + attr.name;
9402
+ }
9403
+ return text;
9404
+ }
9366
9405
  function importOrSelfReferenceName(file, request, name, nameHint) {
9367
9406
  if (isCircularRequest(file, request)) return _marko_compiler.types.identifier(name);
9368
9407
  return (0, _marko_compiler_babel_utils.importNamed)(file, request, name, nameHint);
@@ -9984,6 +10023,7 @@ function getVisitorExit(visit) {
9984
10023
  }
9985
10024
  //#endregion
9986
10025
  //#region src/translator/index.ts
10026
+ const cheatsheet = "../../cheatsheet.md";
9987
10027
  const visitors = extractVisitors({
9988
10028
  Program: program_default,
9989
10029
  Function: function_default,
@@ -10010,6 +10050,7 @@ function getRuntimeEntryFiles(output, optimize) {
10010
10050
  }
10011
10051
  //#endregion
10012
10052
  exports.analyze = analyze;
10053
+ exports.cheatsheet = cheatsheet;
10013
10054
  exports.createInteropTranslator = createInteropTranslator;
10014
10055
  exports.getRuntimeEntryFiles = getRuntimeEntryFiles;
10015
10056
  exports.internalEntryBuilder = entry_builder_default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/runtime-tags",
3
- "version": "6.3.5",
3
+ "version": "6.3.7",
4
4
  "description": "Optimized runtime for Marko templates.",
5
5
  "keywords": [
6
6
  "api",
@@ -51,7 +51,7 @@
51
51
  "build": "node -r ~ts ./scripts/bundle.mts"
52
52
  },
53
53
  "dependencies": {
54
- "@marko/compiler": "^5.41.0",
54
+ "@marko/compiler": "^5.41.1",
55
55
  "csstype": "^3.2.3",
56
56
  "fastest-levenshtein": "^1.0.16",
57
57
  "magic-string": "^0.30.21"