@rozie/babel-plugin 0.2.0 → 0.2.1

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/dist/index.cjs CHANGED
@@ -94234,25 +94234,35 @@ function emitStateField(stateName, init) {
94234
94234
  else if (_babel_types.isObjectExpression(init) && init.properties.length === 0) typeArg = "<any>";
94235
94235
  return ` private _${stateName} = signal${typeArg}(${renderExpression(init)});`;
94236
94236
  }
94237
- function emitRefField(refName, elementTag, composedType) {
94237
+ function emitRefField(refName, elementTag, composedType, hasElementPortal) {
94238
94238
  const field = `_ref${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
94239
- if (composedType) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${composedType};`;
94240
- let domType = "HTMLElement";
94241
- switch (elementTag.toLowerCase()) {
94242
- case "dialog":
94243
- domType = "HTMLDialogElement";
94244
- break;
94245
- case "img":
94246
- domType = "HTMLImageElement";
94247
- break;
94248
- case "ul":
94249
- domType = "HTMLUListElement";
94250
- break;
94251
- case "li":
94252
- domType = "HTMLLIElement";
94253
- break;
94239
+ let domType;
94240
+ if (composedType) domType = composedType;
94241
+ else {
94242
+ domType = "HTMLElement";
94243
+ switch (elementTag.toLowerCase()) {
94244
+ case "dialog":
94245
+ domType = "HTMLDialogElement";
94246
+ break;
94247
+ case "img":
94248
+ domType = "HTMLImageElement";
94249
+ break;
94250
+ case "ul":
94251
+ domType = "HTMLUListElement";
94252
+ break;
94253
+ case "li":
94254
+ domType = "HTMLLIElement";
94255
+ break;
94256
+ }
94254
94257
  }
94255
- return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94258
+ if (!hasElementPortal) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94259
+ const rawField = `__rozieRawRef${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
94260
+ return [
94261
+ ` @query('[data-rozie-ref="${refName}"]') private ${rawField}!: ${domType};`,
94262
+ ` private get ${field}(): ${domType} {`,
94263
+ ` return rozieResolvePortalledRef(this, '[data-rozie-ref="${refName}"]', this.${rawField}) as ${domType};`,
94264
+ " }"
94265
+ ].join("\n");
94256
94266
  }
94257
94267
  function isLifecycleCall(stmt) {
94258
94268
  if (!_babel_types.isExpressionStatement(stmt)) return { isHook: false };
@@ -94538,9 +94548,10 @@ function emitScript(ir, opts) {
94538
94548
  const composedTypeImports = /* @__PURE__ */ new Set();
94539
94549
  for (const ref of ir.refs) {
94540
94550
  const composedType = composedRefs.get(ref.name);
94541
- fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType));
94551
+ fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType, opts.hasElementPortal));
94542
94552
  if (composedType && composedType !== ir.name) composedTypeImports.add(composedType);
94543
94553
  }
94554
+ if (opts.hasElementPortal && ir.refs.length > 0) opts.runtime.add("rozieResolvePortalledRef");
94544
94555
  const composedTypeImportLines = [];
94545
94556
  for (const typeName of composedTypeImports) {
94546
94557
  const decl = (ir.components ?? []).find((c) => c.localName === typeName);
@@ -97178,6 +97189,23 @@ function stringifyRules(rules, source) {
97178
97189
  function escapeCssForTemplateLiteral(css) {
97179
97190
  return css.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
97180
97191
  }
97192
+ /**
97193
+ * FNV-1a 32-bit hash, hex-encoded. Local copy — INTENTIONALLY not shared
97194
+ * with `scopeHash.ts`'s `fnv1a32Hex` (quick 260716-npt Finding 1): that one
97195
+ * hashes `basename::componentName` (name-derived), so two versions of the
97196
+ * same source file collide identically on it. This hash is over the
97197
+ * injected CSS TEXT ITSELF, so distinct global CSS payloads produce
97198
+ * distinct `injectGlobalStyles` runtime dedup ids even when the
97199
+ * componentName is unchanged.
97200
+ */
97201
+ function fnv1a32HexOfCss(s) {
97202
+ let h = 2166136261;
97203
+ for (let i = 0; i < s.length; i++) {
97204
+ h ^= s.charCodeAt(i);
97205
+ h = Math.imul(h, 16777619) >>> 0;
97206
+ }
97207
+ return h.toString(16).padStart(8, "0");
97208
+ }
97181
97209
  function emitStyle(styles, source, opts) {
97182
97210
  const diagnostics = [];
97183
97211
  const scopedRules = styles.scopedRules;
@@ -97203,7 +97231,8 @@ function emitStyle(styles, source, opts) {
97203
97231
  if (globalCss.length > 0) {
97204
97232
  opts.runtime.add("injectGlobalStyles");
97205
97233
  const escaped = escapeCssForTemplateLiteral(globalCss);
97206
- globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-global`}', \`\n${escaped}\n\`);`;
97234
+ const cssHash = fnv1a32HexOfCss(globalCss);
97235
+ globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-${cssHash}-global`}', \`\n${escaped}\n\`);`;
97207
97236
  }
97208
97237
  return {
97209
97238
  staticStylesField,
@@ -97310,12 +97339,13 @@ function emitLit(ir, opts = {}) {
97310
97339
  deconflictReservedComputedInjectNames(ir, reservedClassMembers("lit"), new Set([...(ir.expose ?? []).map((e) => e.name), ...ir.props.map((p) => p.name)]));
97311
97340
  const slotResult = emitSlotDecl(ir, { decorators: decoratorImports });
97312
97341
  diagnostics.push(...slotResult.diagnostics);
97342
+ const componentHasElementPortal = hasElementPortal(ir.template);
97313
97343
  const styleResult = emitStyle(ir.styles, opts.source ?? "", {
97314
97344
  componentName: ir.name,
97315
97345
  lit: litImports,
97316
97346
  runtime: runtimeImports,
97317
97347
  scopeHash,
97318
- hasElementPortal: hasElementPortal(ir.template)
97348
+ hasElementPortal: componentHasElementPortal
97319
97349
  });
97320
97350
  diagnostics.push(...styleResult.diagnostics);
97321
97351
  const scriptResult = emitScript(ir, {
@@ -97324,7 +97354,8 @@ function emitLit(ir, opts = {}) {
97324
97354
  runtime: runtimeImports,
97325
97355
  lit: litImports,
97326
97356
  context: contextImports,
97327
- portalScopeHash: scopeHash
97357
+ portalScopeHash: scopeHash,
97358
+ hasElementPortal: componentHasElementPortal
97328
97359
  });
97329
97360
  diagnostics.push(...scriptResult.diagnostics);
97330
97361
  const listenersResult = emitListeners(ir, {
@@ -97344,8 +97375,8 @@ function emitLit(ir, opts = {}) {
97344
97375
  if (templateResult.rozieSpreadUsed) runtimeImports.add("rozieSpread");
97345
97376
  if (templateResult.rozieListenersUsed) runtimeImports.add("rozieListeners");
97346
97377
  if (ir.adoptDocumentStyles) runtimeImports.add("adoptDocumentStyles");
97347
- const declaredAttrSkipNames = Array.from(new Set(ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()]))).filter((n) => n.length > 0);
97348
- const litAttrsGetter = templateResult.rozieSpreadUsed ? declaredAttrSkipNames.length > 0 ? [
97378
+ const declaredAttrSkipNames = Array.from(new Set(["data-rozie-ref", ...ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()])])).filter((n) => n.length > 0);
97379
+ const litAttrsGetter = templateResult.rozieSpreadUsed ? [
97349
97380
  " /**",
97350
97381
  " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
97351
97382
  " * host custom element's attributes on each call so a consumer-side bound",
@@ -97357,6 +97388,12 @@ function emitLit(ir, opts = {}) {
97357
97388
  " * with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming",
97358
97389
  " * forms are folded into the skip set: kebab-case for model props",
97359
97390
  " * (explicit `attribute:`) AND lowercased property name (Lit's default).",
97391
+ " *",
97392
+ " * command-palette-per-level-virtual / portal-through-portal cluster —",
97393
+ " * `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping",
97394
+ " * attribute, never a consumer prop) so a parent-assigned `ref=` on this",
97395
+ " * component's own host tag can never clobber this component's OWN",
97396
+ " * internal `data-rozie-ref` ref markers via fallthrough re-application.",
97360
97397
  " */",
97361
97398
  " private get $attrs(): Record<string, string> {",
97362
97399
  ` const __skip = new Set<string>([${declaredAttrSkipNames.map((n) => `'${n}'`).join(", ")}]);`,
@@ -97367,18 +97404,6 @@ function emitLit(ir, opts = {}) {
97367
97404
  " }",
97368
97405
  " return out;",
97369
97406
  " }"
97370
- ].join("\n") : [
97371
- " /**",
97372
- " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
97373
- " * host custom element's attributes on each call so a consumer-side bound",
97374
- " * attribute flows through on every render. The `rozieSpread` directive",
97375
- " * (D-02) does the cross-render diff downstream.",
97376
- " */",
97377
- " private get $attrs(): Record<string, string> {",
97378
- " const out: Record<string, string> = {};",
97379
- " for (const a of Array.from(this.attributes)) out[a.name] = a.value;",
97380
- " return out;",
97381
- " }"
97382
97407
  ].join("\n") : "";
97383
97408
  const litListenersGetter = templateResult.rozieListenersUsed ? [
97384
97409
  " /**",
package/dist/index.mjs CHANGED
@@ -94160,25 +94160,35 @@ function emitStateField(stateName, init) {
94160
94160
  else if (t$3.isObjectExpression(init) && init.properties.length === 0) typeArg = "<any>";
94161
94161
  return ` private _${stateName} = signal${typeArg}(${renderExpression(init)});`;
94162
94162
  }
94163
- function emitRefField(refName, elementTag, composedType) {
94163
+ function emitRefField(refName, elementTag, composedType, hasElementPortal) {
94164
94164
  const field = `_ref${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
94165
- if (composedType) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${composedType};`;
94166
- let domType = "HTMLElement";
94167
- switch (elementTag.toLowerCase()) {
94168
- case "dialog":
94169
- domType = "HTMLDialogElement";
94170
- break;
94171
- case "img":
94172
- domType = "HTMLImageElement";
94173
- break;
94174
- case "ul":
94175
- domType = "HTMLUListElement";
94176
- break;
94177
- case "li":
94178
- domType = "HTMLLIElement";
94179
- break;
94165
+ let domType;
94166
+ if (composedType) domType = composedType;
94167
+ else {
94168
+ domType = "HTMLElement";
94169
+ switch (elementTag.toLowerCase()) {
94170
+ case "dialog":
94171
+ domType = "HTMLDialogElement";
94172
+ break;
94173
+ case "img":
94174
+ domType = "HTMLImageElement";
94175
+ break;
94176
+ case "ul":
94177
+ domType = "HTMLUListElement";
94178
+ break;
94179
+ case "li":
94180
+ domType = "HTMLLIElement";
94181
+ break;
94182
+ }
94180
94183
  }
94181
- return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94184
+ if (!hasElementPortal) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94185
+ const rawField = `__rozieRawRef${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
94186
+ return [
94187
+ ` @query('[data-rozie-ref="${refName}"]') private ${rawField}!: ${domType};`,
94188
+ ` private get ${field}(): ${domType} {`,
94189
+ ` return rozieResolvePortalledRef(this, '[data-rozie-ref="${refName}"]', this.${rawField}) as ${domType};`,
94190
+ " }"
94191
+ ].join("\n");
94182
94192
  }
94183
94193
  function isLifecycleCall(stmt) {
94184
94194
  if (!t$3.isExpressionStatement(stmt)) return { isHook: false };
@@ -94464,9 +94474,10 @@ function emitScript(ir, opts) {
94464
94474
  const composedTypeImports = /* @__PURE__ */ new Set();
94465
94475
  for (const ref of ir.refs) {
94466
94476
  const composedType = composedRefs.get(ref.name);
94467
- fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType));
94477
+ fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType, opts.hasElementPortal));
94468
94478
  if (composedType && composedType !== ir.name) composedTypeImports.add(composedType);
94469
94479
  }
94480
+ if (opts.hasElementPortal && ir.refs.length > 0) opts.runtime.add("rozieResolvePortalledRef");
94470
94481
  const composedTypeImportLines = [];
94471
94482
  for (const typeName of composedTypeImports) {
94472
94483
  const decl = (ir.components ?? []).find((c) => c.localName === typeName);
@@ -97104,6 +97115,23 @@ function stringifyRules(rules, source) {
97104
97115
  function escapeCssForTemplateLiteral(css) {
97105
97116
  return css.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
97106
97117
  }
97118
+ /**
97119
+ * FNV-1a 32-bit hash, hex-encoded. Local copy — INTENTIONALLY not shared
97120
+ * with `scopeHash.ts`'s `fnv1a32Hex` (quick 260716-npt Finding 1): that one
97121
+ * hashes `basename::componentName` (name-derived), so two versions of the
97122
+ * same source file collide identically on it. This hash is over the
97123
+ * injected CSS TEXT ITSELF, so distinct global CSS payloads produce
97124
+ * distinct `injectGlobalStyles` runtime dedup ids even when the
97125
+ * componentName is unchanged.
97126
+ */
97127
+ function fnv1a32HexOfCss(s) {
97128
+ let h = 2166136261;
97129
+ for (let i = 0; i < s.length; i++) {
97130
+ h ^= s.charCodeAt(i);
97131
+ h = Math.imul(h, 16777619) >>> 0;
97132
+ }
97133
+ return h.toString(16).padStart(8, "0");
97134
+ }
97107
97135
  function emitStyle(styles, source, opts) {
97108
97136
  const diagnostics = [];
97109
97137
  const scopedRules = styles.scopedRules;
@@ -97129,7 +97157,8 @@ function emitStyle(styles, source, opts) {
97129
97157
  if (globalCss.length > 0) {
97130
97158
  opts.runtime.add("injectGlobalStyles");
97131
97159
  const escaped = escapeCssForTemplateLiteral(globalCss);
97132
- globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-global`}', \`\n${escaped}\n\`);`;
97160
+ const cssHash = fnv1a32HexOfCss(globalCss);
97161
+ globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-${cssHash}-global`}', \`\n${escaped}\n\`);`;
97133
97162
  }
97134
97163
  return {
97135
97164
  staticStylesField,
@@ -97236,12 +97265,13 @@ function emitLit(ir, opts = {}) {
97236
97265
  deconflictReservedComputedInjectNames(ir, reservedClassMembers("lit"), new Set([...(ir.expose ?? []).map((e) => e.name), ...ir.props.map((p) => p.name)]));
97237
97266
  const slotResult = emitSlotDecl(ir, { decorators: decoratorImports });
97238
97267
  diagnostics.push(...slotResult.diagnostics);
97268
+ const componentHasElementPortal = hasElementPortal(ir.template);
97239
97269
  const styleResult = emitStyle(ir.styles, opts.source ?? "", {
97240
97270
  componentName: ir.name,
97241
97271
  lit: litImports,
97242
97272
  runtime: runtimeImports,
97243
97273
  scopeHash,
97244
- hasElementPortal: hasElementPortal(ir.template)
97274
+ hasElementPortal: componentHasElementPortal
97245
97275
  });
97246
97276
  diagnostics.push(...styleResult.diagnostics);
97247
97277
  const scriptResult = emitScript(ir, {
@@ -97250,7 +97280,8 @@ function emitLit(ir, opts = {}) {
97250
97280
  runtime: runtimeImports,
97251
97281
  lit: litImports,
97252
97282
  context: contextImports,
97253
- portalScopeHash: scopeHash
97283
+ portalScopeHash: scopeHash,
97284
+ hasElementPortal: componentHasElementPortal
97254
97285
  });
97255
97286
  diagnostics.push(...scriptResult.diagnostics);
97256
97287
  const listenersResult = emitListeners(ir, {
@@ -97270,8 +97301,8 @@ function emitLit(ir, opts = {}) {
97270
97301
  if (templateResult.rozieSpreadUsed) runtimeImports.add("rozieSpread");
97271
97302
  if (templateResult.rozieListenersUsed) runtimeImports.add("rozieListeners");
97272
97303
  if (ir.adoptDocumentStyles) runtimeImports.add("adoptDocumentStyles");
97273
- const declaredAttrSkipNames = Array.from(new Set(ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()]))).filter((n) => n.length > 0);
97274
- const litAttrsGetter = templateResult.rozieSpreadUsed ? declaredAttrSkipNames.length > 0 ? [
97304
+ const declaredAttrSkipNames = Array.from(new Set(["data-rozie-ref", ...ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()])])).filter((n) => n.length > 0);
97305
+ const litAttrsGetter = templateResult.rozieSpreadUsed ? [
97275
97306
  " /**",
97276
97307
  " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
97277
97308
  " * host custom element's attributes on each call so a consumer-side bound",
@@ -97283,6 +97314,12 @@ function emitLit(ir, opts = {}) {
97283
97314
  " * with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming",
97284
97315
  " * forms are folded into the skip set: kebab-case for model props",
97285
97316
  " * (explicit `attribute:`) AND lowercased property name (Lit's default).",
97317
+ " *",
97318
+ " * command-palette-per-level-virtual / portal-through-portal cluster —",
97319
+ " * `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping",
97320
+ " * attribute, never a consumer prop) so a parent-assigned `ref=` on this",
97321
+ " * component's own host tag can never clobber this component's OWN",
97322
+ " * internal `data-rozie-ref` ref markers via fallthrough re-application.",
97286
97323
  " */",
97287
97324
  " private get $attrs(): Record<string, string> {",
97288
97325
  ` const __skip = new Set<string>([${declaredAttrSkipNames.map((n) => `'${n}'`).join(", ")}]);`,
@@ -97293,18 +97330,6 @@ function emitLit(ir, opts = {}) {
97293
97330
  " }",
97294
97331
  " return out;",
97295
97332
  " }"
97296
- ].join("\n") : [
97297
- " /**",
97298
- " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
97299
- " * host custom element's attributes on each call so a consumer-side bound",
97300
- " * attribute flows through on every render. The `rozieSpread` directive",
97301
- " * (D-02) does the cross-render diff downstream.",
97302
- " */",
97303
- " private get $attrs(): Record<string, string> {",
97304
- " const out: Record<string, string> = {};",
97305
- " for (const a of Array.from(this.attributes)) out[a.name] = a.value;",
97306
- " return out;",
97307
- " }"
97308
97333
  ].join("\n") : "";
97309
97334
  const litListenersGetter = templateResult.rozieListenersUsed ? [
97310
97335
  " /**",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozie/babel-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Babel plugin for Rozie.js — resolves .rozie imports and compiles them to idiomatic target-framework code.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@babel/helper-plugin-utils": "^7.28.0",
27
- "@rozie/core": "0.2.0"
27
+ "@rozie/core": "0.2.1"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@babel/core": "^7.29.0"