@rozie/babel-plugin 0.1.4 → 0.2.0

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.
Files changed (3) hide show
  1. package/dist/index.cjs +1220 -55
  2. package/dist/index.mjs +1209 -44
  3. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -1592,7 +1592,12 @@ const RozieErrorCode = {
1592
1592
  KEYNAV_ORPHAN_ITEM: "ROZ984",
1593
1593
  KEYNAV_BAD_FOCUS_MODEL: "ROZ985",
1594
1594
  KEYNAV_MULTIPLE_ROOTS: "ROZ986",
1595
- KEYNAV_SOURCE_UNRESOLVED: "ROZ987"
1595
+ KEYNAV_SOURCE_UNRESOLVED: "ROZ987",
1596
+ MANIFEST_SCHEMA_VERSION_MISMATCH: "ROZ988",
1597
+ MALFORMED_MANIFEST: "ROZ989",
1598
+ PORTAL_DIRECTIVE_ON_SLOT: "ROZ990",
1599
+ PORTAL_DIRECTIVE_ON_COMPONENT: "ROZ991",
1600
+ PORTAL_DIRECTIVE_EMPTY_VALUE: "ROZ992"
1596
1601
  };
1597
1602
  //#endregion
1598
1603
  //#region ../core/src/splitter/splitBlocks.ts
@@ -19047,8 +19052,8 @@ var require_source_map$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
19047
19052
  //#endregion
19048
19053
  //#region ../../node_modules/.pnpm/postcss@8.5.13/node_modules/postcss/lib/previous-map.js
19049
19054
  var require_previous_map = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19050
- let { existsSync: existsSync$1, readFileSync: readFileSync$3 } = require("fs");
19051
- let { dirname: dirname$4, join: join$1 } = require("path");
19055
+ let { existsSync: existsSync$1, readFileSync: readFileSync$4 } = require("fs");
19056
+ let { dirname: dirname$5, join: join$2 } = require("path");
19052
19057
  let { SourceMapConsumer, SourceMapGenerator } = require_source_map$1();
19053
19058
  function fromBase64(str) {
19054
19059
  if (Buffer) return Buffer.from(str, "base64").toString();
@@ -19065,7 +19070,7 @@ var require_previous_map = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19065
19070
  let prev = opts.map ? opts.map.prev : void 0;
19066
19071
  let text = this.loadMap(opts.from, prev);
19067
19072
  if (!this.mapFile && opts.from) this.mapFile = opts.from;
19068
- if (this.mapFile) this.root = dirname$4(this.mapFile);
19073
+ if (this.mapFile) this.root = dirname$5(this.mapFile);
19069
19074
  if (text) this.text = text;
19070
19075
  }
19071
19076
  consumer() {
@@ -19102,10 +19107,10 @@ var require_previous_map = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19102
19107
  if (!trusted && !this.unsafeMap) {
19103
19108
  if (!/\.map$/i.test(path$5)) return;
19104
19109
  }
19105
- this.root = dirname$4(path$5);
19110
+ this.root = dirname$5(path$5);
19106
19111
  if (existsSync$1(path$5)) {
19107
19112
  this.mapFile = path$5;
19108
- return readFileSync$3(path$5, "utf-8").toString().trim();
19113
+ return readFileSync$4(path$5, "utf-8").toString().trim();
19109
19114
  }
19110
19115
  }
19111
19116
  loadMap(file, prev) {
@@ -19125,7 +19130,7 @@ var require_previous_map = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19125
19130
  else if (this.inline) return this.decodeInline(this.annotation);
19126
19131
  else if (this.annotation) {
19127
19132
  let map = this.annotation;
19128
- if (file) map = join$1(dirname$4(file), map);
19133
+ if (file) map = join$2(dirname$5(file), map);
19129
19134
  let unknown = this.loadFile(map, file, false);
19130
19135
  if (unknown) try {
19131
19136
  /* c8 ignore next 4 */
@@ -19497,12 +19502,12 @@ var require_fromJSON = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19497
19502
  //#endregion
19498
19503
  //#region ../../node_modules/.pnpm/postcss@8.5.13/node_modules/postcss/lib/map-generator.js
19499
19504
  var require_map_generator = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19500
- let { dirname: dirname$3, relative, resolve: resolve$1, sep } = require("path");
19505
+ let { dirname: dirname$4, relative, resolve: resolve$1, sep } = require("path");
19501
19506
  let { SourceMapConsumer, SourceMapGenerator } = require_source_map$1();
19502
19507
  let { pathToFileURL } = require("url");
19503
19508
  let Input = require_input();
19504
19509
  let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator);
19505
- let pathAvailable = Boolean(dirname$3 && resolve$1 && relative && sep);
19510
+ let pathAvailable = Boolean(dirname$4 && resolve$1 && relative && sep);
19506
19511
  var MapGenerator = class {
19507
19512
  constructor(stringify, root, opts, cssString) {
19508
19513
  this.stringify = stringify;
@@ -19529,7 +19534,7 @@ var require_map_generator = /* @__PURE__ */ __commonJSMin(((exports, module) =>
19529
19534
  applyPrevMaps() {
19530
19535
  for (let prev of this.previous()) {
19531
19536
  let from = this.toUrl(this.path(prev.file));
19532
- let root = prev.root || dirname$3(prev.file);
19537
+ let root = prev.root || dirname$4(prev.file);
19533
19538
  let map;
19534
19539
  if (this.mapOpts.sourcesContent === false) {
19535
19540
  map = new SourceMapConsumer(prev.text);
@@ -19694,8 +19699,8 @@ var require_map_generator = /* @__PURE__ */ __commonJSMin(((exports, module) =>
19694
19699
  if (/^\w+:\/\//.test(file)) return file;
19695
19700
  let cached = this.memoizedPaths.get(file);
19696
19701
  if (cached) return cached;
19697
- let from = this.opts.to ? dirname$3(this.opts.to) : ".";
19698
- if (typeof this.mapOpts.annotation === "string") from = dirname$3(resolve$1(from, this.mapOpts.annotation));
19702
+ let from = this.opts.to ? dirname$4(this.opts.to) : ".";
19703
+ if (typeof this.mapOpts.annotation === "string") from = dirname$4(resolve$1(from, this.mapOpts.annotation));
19699
19704
  let path$2 = relative(from, file);
19700
19705
  this.memoizedPaths.set(file, path$2);
19701
19706
  return path$2;
@@ -41266,6 +41271,13 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41266
41271
  const args = [];
41267
41272
  for (const attr of el.attributes) if (attr.kind === "static" && attr.name === "name" && attr.value !== null) slotName = attr.value;
41268
41273
  else if (attr.kind === "static" && attr.name === "portal" && attr.value === null) isPortal = true;
41274
+ else if (attr.kind === "directive" && attr.name === "portal") diagnostics.push({
41275
+ code: RozieErrorCode.PORTAL_DIRECTIVE_ON_SLOT,
41276
+ severity: "error",
41277
+ message: "r-portal is not valid on <slot> — use the boolean `portal` attribute for slot-content portalling (r-portal is a distinct directive for portalling an ordinary element's own rendered subtree).",
41278
+ loc: attr.loc,
41279
+ hint: "Use <slot portal /> (no r-, boolean form) to portal slot content into an engine-owned container; r-portal=\"<expr>\" portals an ordinary element out to a container."
41280
+ });
41269
41281
  else if (attr.kind === "binding" && attr.value !== null) {
41270
41282
  if (attr.name === "params") continue;
41271
41283
  const expr = tryParseExpression(attr.value);
@@ -41296,6 +41308,8 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41296
41308
  let keynavItem;
41297
41309
  let keynavActiveClassExpression;
41298
41310
  let keynavActiveClassDeps;
41311
+ let portalToExpression;
41312
+ let portalToDeps;
41299
41313
  for (const attr of el.attributes) {
41300
41314
  if (attr.kind === "event") {
41301
41315
  const handlerExpr = attr.value !== null ? tryParseExpression(attr.value) : null;
@@ -41505,6 +41519,34 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41505
41519
  isExternal = true;
41506
41520
  continue;
41507
41521
  }
41522
+ if (attr.name === "portal") {
41523
+ if (attr.value === null || attr.value.trim() === "") {
41524
+ diagnostics.push({
41525
+ code: RozieErrorCode.PORTAL_DIRECTIVE_EMPTY_VALUE,
41526
+ severity: "error",
41527
+ message: "r-portal requires a container expression (e.g. r-portal=\"$props.appendTo\") — an empty value is not a valid portal target.",
41528
+ loc: attr.loc,
41529
+ hint: "Provide an expression that evaluates to an Element (or a falsy value / null to render in place)."
41530
+ });
41531
+ continue;
41532
+ }
41533
+ if (elementIsComponent) {
41534
+ diagnostics.push({
41535
+ code: RozieErrorCode.PORTAL_DIRECTIVE_ON_COMPONENT,
41536
+ severity: "error",
41537
+ message: "r-portal is not supported on a <components>-registered child component element (v1 limitation) — only plain/host elements can be portalled.",
41538
+ loc: attr.loc,
41539
+ hint: "Wrap the component in a plain host element and put r-portal on that wrapper instead."
41540
+ });
41541
+ continue;
41542
+ }
41543
+ const expr = tryParseExpression(attr.value);
41544
+ if (expr) {
41545
+ portalToExpression = expr;
41546
+ portalToDeps = computeExpressionDeps(expr, bindings);
41547
+ }
41548
+ continue;
41549
+ }
41508
41550
  if ((attr.name === "model" || attr.name === "show" || attr.name === "html" || attr.name === "text") && attr.value !== null) {
41509
41551
  const expr = tryParseExpression(attr.value);
41510
41552
  const modifiers = attr.name === "model" ? resolveModelModifiers(attr.chain, registry, diagnostics) : [];
@@ -41556,7 +41598,11 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41556
41598
  ...isExternal ? { isExternal: true } : {},
41557
41599
  ...remountKeyExpression !== void 0 ? { remountKeyExpression } : {},
41558
41600
  ...keynavRoot !== void 0 ? { keynavRoot } : {},
41559
- ...keynavItem !== void 0 ? { keynavItem } : {}
41601
+ ...keynavItem !== void 0 ? { keynavItem } : {},
41602
+ ...portalToExpression !== void 0 ? { portalTo: {
41603
+ expression: portalToExpression,
41604
+ deps: portalToDeps ?? []
41605
+ } } : {}
41560
41606
  };
41561
41607
  }
41562
41608
  /**
@@ -55882,6 +55928,340 @@ var IRCache = class {
55882
55928
  }
55883
55929
  };
55884
55930
  //#endregion
55931
+ //#region ../core/src/manifest/buildManifest.ts
55932
+ var import_lib = /* @__PURE__ */ __toESM(require_lib$6(), 1);
55933
+ typeof import_lib.default === "function" ? import_lib.default : import_lib.default.default;
55934
+ //#endregion
55935
+ //#region ../core/src/manifest/readManifest.ts
55936
+ /**
55937
+ * parseManifest(raw) — validate + deserialize a published-primitive manifest
55938
+ * into a producer slot/prop/expose surface (Phase 75, D-04).
55939
+ *
55940
+ * Fails CLOSED on any trust-boundary violation (T-75-01): an unknown or
55941
+ * mismatched `schemaVersion` — or any structurally malformed field — never
55942
+ * silently degrades into a partial/best-effort surface. Both failure modes
55943
+ * return `{ surface: null, error }`; parseManifest NEVER throws (T-75-02: a
55944
+ * crafted/oversized paramType source string is dropped per-entry, not fatal).
55945
+ *
55946
+ * Per T-75-03, every field is read via explicit key access into a FRESH
55947
+ * object — parseManifest never `Object.assign`/spreads the parsed JSON into
55948
+ * a shared target, so a `__proto__`/`constructor`-keyed manifest cannot
55949
+ * pollute anything beyond the literal-keyed surface it produces.
55950
+ *
55951
+ * @experimental — shape may change before v1.0
55952
+ */
55953
+ function malformed(message) {
55954
+ return {
55955
+ surface: null,
55956
+ error: {
55957
+ code: RozieErrorCode.MALFORMED_MANIFEST,
55958
+ message
55959
+ }
55960
+ };
55961
+ }
55962
+ function schemaMismatch(message) {
55963
+ return {
55964
+ surface: null,
55965
+ error: {
55966
+ code: RozieErrorCode.MANIFEST_SCHEMA_VERSION_MISMATCH,
55967
+ message
55968
+ }
55969
+ };
55970
+ }
55971
+ function isPlainObject(v) {
55972
+ return typeof v === "object" && v !== null && !Array.isArray(v);
55973
+ }
55974
+ /**
55975
+ * Deserialize one paramType SOURCE STRING back into a `TSType` by parsing
55976
+ * `type __RozieParam = <src>;` and lifting the type-alias's typeAnnotation.
55977
+ * Wrapped in try/catch — a garbage/oversized string is dropped (returns
55978
+ * `null`) rather than throwing out of parseManifest (T-75-02).
55979
+ */
55980
+ function deserializeParamType(src) {
55981
+ try {
55982
+ const stmt = (0, import_lib$3.parse)(`type __RozieParam = ${src};`, {
55983
+ sourceType: "module",
55984
+ plugins: ["typescript"]
55985
+ }).program.body[0];
55986
+ if (!stmt || !_babel_types.isTSTypeAliasDeclaration(stmt)) return null;
55987
+ return stmt.typeAnnotation;
55988
+ } catch {
55989
+ return null;
55990
+ }
55991
+ }
55992
+ /** Validate + rebuild one manifest prop entry. Returns null on structural violation. */
55993
+ function readProp(raw) {
55994
+ if (!isPlainObject(raw)) return null;
55995
+ const name = raw.name;
55996
+ const isModel = raw.isModel;
55997
+ if (typeof name !== "string") return null;
55998
+ if (typeof isModel !== "boolean") return null;
55999
+ return {
56000
+ name,
56001
+ isModel
56002
+ };
56003
+ }
56004
+ /** Validate + rebuild one manifest slot-param entry. Returns null on structural violation. */
56005
+ function readSlotParam(raw) {
56006
+ if (!isPlainObject(raw)) return null;
56007
+ const name = raw.name;
56008
+ if (typeof name !== "string") return null;
56009
+ return {
56010
+ type: "ParamDecl",
56011
+ name,
56012
+ valueExpression: _babel_types.identifier(name),
56013
+ sourceLoc: {
56014
+ start: 0,
56015
+ end: 0
56016
+ }
56017
+ };
56018
+ }
56019
+ /** Validate + rebuild one manifest slot entry into a minimal SlotDecl. */
56020
+ function readSlot(raw) {
56021
+ if (!isPlainObject(raw)) return null;
56022
+ const name = raw.name;
56023
+ const paramsRaw = raw.params;
56024
+ const paramTypesRaw = raw.paramTypes;
56025
+ const isPortal = raw.isPortal;
56026
+ const isReactive = raw.isReactive;
56027
+ if (typeof name !== "string") return null;
56028
+ if (!Array.isArray(paramsRaw)) return null;
56029
+ if (paramTypesRaw !== null && !Array.isArray(paramTypesRaw)) return null;
56030
+ if (typeof isPortal !== "boolean") return null;
56031
+ if (typeof isReactive !== "boolean") return null;
56032
+ const params = [];
56033
+ for (const p of paramsRaw) {
56034
+ const param = readSlotParam(p);
56035
+ if (param === null) return null;
56036
+ params.push(param);
56037
+ }
56038
+ let paramTypes;
56039
+ if (paramTypesRaw !== null) {
56040
+ const deserialized = [];
56041
+ for (const src of paramTypesRaw) {
56042
+ if (typeof src !== "string") continue;
56043
+ const ty = deserializeParamType(src);
56044
+ if (ty !== null) deserialized.push(ty);
56045
+ }
56046
+ paramTypes = deserialized;
56047
+ }
56048
+ return {
56049
+ type: "SlotDecl",
56050
+ name,
56051
+ defaultContent: null,
56052
+ params,
56053
+ ...paramTypes !== void 0 ? { paramTypes } : {},
56054
+ presence: "always",
56055
+ nestedSlots: [],
56056
+ sourceLoc: {
56057
+ start: 0,
56058
+ end: 0
56059
+ },
56060
+ isPortal,
56061
+ isReactive
56062
+ };
56063
+ }
56064
+ /** Validate + rebuild one manifest expose entry. Returns null on structural violation. */
56065
+ function readExposeMember(raw) {
56066
+ if (!isPlainObject(raw)) return null;
56067
+ const name = raw.name;
56068
+ if (typeof name !== "string") return null;
56069
+ return { name };
56070
+ }
56071
+ /**
56072
+ * Validate a manifest's `schemaVersion` FIRST (fail closed, D-04), then
56073
+ * shape-validate props/slots/emits/expose and deserialize into a
56074
+ * `ProducerSurface` consumable by `threadParamTypes` / `validateTwoWayBindings`.
56075
+ * Never throws (T-75-01/T-75-02).
56076
+ */
56077
+ function parseManifest(raw, _opts) {
56078
+ if (!isPlainObject(raw)) return malformed("Manifest is not a JSON object.");
56079
+ const schemaVersion = raw.schemaVersion;
56080
+ if (typeof schemaVersion !== "number") return malformed("Manifest is missing a numeric \"schemaVersion\" field.");
56081
+ if (schemaVersion !== 1) return schemaMismatch(`Manifest schemaVersion ${schemaVersion} is incompatible with the compiler's MANIFEST_SCHEMA_VERSION 1. Reinstall a compatible version of the published primitive.`);
56082
+ const propsRaw = raw.props;
56083
+ const slotsRaw = raw.slots;
56084
+ const emitsRaw = raw.emits;
56085
+ const exposeRaw = raw.expose;
56086
+ if (!Array.isArray(propsRaw)) return malformed("Manifest \"props\" is not an array.");
56087
+ if (!Array.isArray(slotsRaw)) return malformed("Manifest \"slots\" is not an array.");
56088
+ if (!Array.isArray(emitsRaw)) return malformed("Manifest \"emits\" is not an array.");
56089
+ if (!Array.isArray(exposeRaw)) return malformed("Manifest \"expose\" is not an array.");
56090
+ const props = [];
56091
+ for (const p of propsRaw) {
56092
+ const prop = readProp(p);
56093
+ if (prop === null) return malformed("Manifest \"props\" entry is malformed.");
56094
+ props.push(prop);
56095
+ }
56096
+ const slots = [];
56097
+ for (const s of slotsRaw) {
56098
+ const slot = readSlot(s);
56099
+ if (slot === null) return malformed("Manifest \"slots\" entry is malformed.");
56100
+ slots.push(slot);
56101
+ }
56102
+ for (const e of emitsRaw) if (typeof e !== "string") return malformed("Manifest \"emits\" entry is not a string.");
56103
+ const expose = [];
56104
+ for (const m of exposeRaw) {
56105
+ const member = readExposeMember(m);
56106
+ if (member === null) return malformed("Manifest \"expose\" entry is malformed.");
56107
+ expose.push(member);
56108
+ }
56109
+ return {
56110
+ surface: {
56111
+ slots,
56112
+ props,
56113
+ expose
56114
+ },
56115
+ error: null
56116
+ };
56117
+ }
56118
+ //#endregion
56119
+ //#region ../core/src/manifest/resolveManifestProducer.ts
56120
+ /**
56121
+ * resolveManifestProducer — Phase 75 Plan 02 (D-08/D-09/D-10).
56122
+ *
56123
+ * Wires the manifest contract (Plan 01's `buildManifest`/`parseManifest`)
56124
+ * into `@rozie/core`'s producer-resolution seam so a PUBLISHED cross-package
56125
+ * `<components>` specifier resolves a producer contract from a compiled
56126
+ * `rozie-manifest.json` instead of a `.rozie` source file.
56127
+ *
56128
+ * **COMP-04 bounded exception**: local `.rozie` composition (relative /
56129
+ * tsconfig-alias specifiers) still needs NO cross-`.rozie` IR type info and
56130
+ * keeps reading on-disk `.rozie` source unchanged via the existing
56131
+ * `resolver.resolveProducerPath` + `IRCache.getIRComponent` path. ONLY
56132
+ * published cross-PACKAGE specifiers (D-08's `@scope/pkg/Component.rozie`
56133
+ * shape) read this compiled manifest artifact. This module and its two
56134
+ * call-sites (threadParamTypes.ts, validateTwoWayBindings.ts) are the entire
56135
+ * surface of that exception.
56136
+ *
56137
+ * Convention-based per-target package derivation (D-08): for a published
56138
+ * specifier `@scope/pkg/Component.rozie`, the trailing `/Component.rozie`
56139
+ * segment is stripped to yield the base package `@scope/pkg`, then
56140
+ * `-${target}` is appended to yield the derived per-target package name
56141
+ * (e.g. `@rozie-ui/combobox` + `-react` → `@rozie-ui/combobox-react`).
56142
+ *
56143
+ * Location (D-10): the derived package is located via the SAME
56144
+ * `enhanced-resolve` layer `ProducerResolver` already uses for `.rozie`
56145
+ * producer resolution — walking `node_modules` from the consumer file's
56146
+ * directory (NOT from `ResolverOptions.root`, which feeds only the
56147
+ * tsconfig-paths matcher). Because a real per-target leaf's `package.json`
56148
+ * `exports` map does not (and need not) list `"./package.json"` or
56149
+ * `"./rozie-manifest.json"` as exported subpaths, this resolver deliberately
56150
+ * resolves the package's bare `"."` export entry (its normal `import`
56151
+ * condition target, e.g. `dist/index.mjs`) and then walks UP from that
56152
+ * resolved file to the nearest ancestor directory whose `package.json`
56153
+ * `name` matches the derived package name (T-75-04/T-75-05 — bounded walk,
56154
+ * name-verified, never an unauthenticated `../` traversal). The manifest is
56155
+ * then read at a FIXED literal relative path inside that directory
56156
+ * (`rozie-manifest.json`) — no specifier-derived path segment ever reaches
56157
+ * `readFileSync` (T-75-04).
56158
+ *
56159
+ * @experimental — shape may change before v1.0
56160
+ */
56161
+ /** Tsconfig-alias-shaped prefix (empty npm scope, e.g. `@/components/Modal.rozie`). */
56162
+ const TSCONFIG_ALIAS_PREFIX$1 = "@/";
56163
+ /**
56164
+ * True iff `specifier` is a PUBLISHED cross-package `<components>` specifier
56165
+ * (D-08 shape): ends in `.rozie`, is neither a relative/absolute path nor a
56166
+ * tsconfig `paths`-alias-shaped specifier.
56167
+ *
56168
+ * `isPublishedSpecifier` is a pure, syntactic classifier — it does not itself
56169
+ * consult a real tsconfig (that happens inside `ProducerResolver`'s local
56170
+ * COMP-04 path when this returns `false`). A leading `@/` is NOT a valid npm
56171
+ * scoped-package prefix (npm scopes require at least one character between
56172
+ * `@` and the following `/`), so it is the reliable syntactic signal that a
56173
+ * `@/...` specifier is tsconfig-alias-shaped rather than a real scoped
56174
+ * package — matching `ProducerResolver`'s own documented `@/components/Modal.rozie`
56175
+ * example of a tsconfig-paths alias.
56176
+ */
56177
+ function isPublishedSpecifier$1(specifier) {
56178
+ if (!specifier.endsWith(".rozie")) return false;
56179
+ if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
56180
+ if (specifier.startsWith(TSCONFIG_ALIAS_PREFIX$1)) return false;
56181
+ return true;
56182
+ }
56183
+ /**
56184
+ * Strip the trailing `/<Component>.rozie` leaf off a published specifier to
56185
+ * derive the base package name (e.g. `@rozie-ui/combobox/Combobox.rozie` →
56186
+ * `@rozie-ui/combobox`). Returns `null` when the specifier has no `/`
56187
+ * separating a package from a leaf (malformed — never a valid published
56188
+ * specifier in practice, since `isPublishedSpecifier` requires `.rozie`).
56189
+ */
56190
+ function deriveBasePackageName(specifier) {
56191
+ const lastSlash = specifier.lastIndexOf("/");
56192
+ if (lastSlash <= 0) return null;
56193
+ return specifier.slice(0, lastSlash);
56194
+ }
56195
+ /**
56196
+ * Walk UP from a resolved entry-file path to the nearest ancestor directory
56197
+ * whose `package.json` `name` field equals `expectedName` AND which carries a
56198
+ * sibling `rozie-manifest.json`. Bounded to 10 levels (T-75-05 — a real
56199
+ * per-target leaf's package root sits 1-3 levels above its resolved `dist/*`
56200
+ * entry; 10 is a generous, still-finite bound that can never escape into an
56201
+ * unrelated ancestor tree undetected, since EVERY level is name-verified
56202
+ * before being accepted).
56203
+ *
56204
+ * Requiring the manifest sibling (not just a name match) is deliberate: some
56205
+ * per-target build tooling generates a SECONDARY `package.json` inside the
56206
+ * `dist/` output with the SAME `name` field as the real package root (e.g.
56207
+ * `ng-packagr`'s Angular `Package Format v9+` legacy-entry-point convention
56208
+ * emits `dist/package.json`). A name-only match would stop at that inner
56209
+ * duplicate — one level too shallow — and never reach the real package root
56210
+ * where `rozie-manifest.json` actually lives (only shipped at the root,
56211
+ * sibling to the AUTHORED `package.json`, never duplicated into `dist/`).
56212
+ * Continuing the walk past a name-matched-but-manifest-less directory finds
56213
+ * the correct root for every current `@rozie-ui/*-<target>` leaf shape.
56214
+ */
56215
+ function findPackageRoot(entryFilePath, expectedName) {
56216
+ let dir = (0, node_path.dirname)(entryFilePath);
56217
+ for (let i = 0; i < 10; i++) {
56218
+ try {
56219
+ const raw = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(dir, "package.json"), "utf-8"));
56220
+ if (typeof raw === "object" && raw !== null && raw.name === expectedName) try {
56221
+ (0, node_fs.readFileSync)((0, node_path.join)(dir, "rozie-manifest.json"), "utf-8");
56222
+ return dir;
56223
+ } catch {}
56224
+ } catch {}
56225
+ const parent = (0, node_path.dirname)(dir);
56226
+ if (parent === dir) return null;
56227
+ dir = parent;
56228
+ }
56229
+ return null;
56230
+ }
56231
+ function lookupFailed(pkgLabel) {
56232
+ return {
56233
+ surface: null,
56234
+ error: {
56235
+ code: RozieErrorCode.CROSS_PACKAGE_LOOKUP_FAILED,
56236
+ message: `Cannot locate a published manifest for '${pkgLabel}'. Verify the per-target package is installed as a dependency reachable from the consumer file and ships a rozie-manifest.json.`
56237
+ }
56238
+ };
56239
+ }
56240
+ /**
56241
+ * Derive the per-target package for a published `<components>` specifier,
56242
+ * locate its installed `rozie-manifest.json` via node module resolution, and
56243
+ * validate + deserialize it into a `ProducerSurface` (D-08/D-09/D-10/D-04).
56244
+ *
56245
+ * Never throws — every failure mode returns `{ surface: null, error }`.
56246
+ */
56247
+ function resolveManifestProducer(args) {
56248
+ const { specifier, target, fromFile, resolver } = args;
56249
+ const basePkg = deriveBasePackageName(specifier);
56250
+ if (basePkg === null) return lookupFailed(specifier);
56251
+ const derivedPkg = `${basePkg}-${target}`;
56252
+ const entryFilePath = resolver.resolveProducerPath(derivedPkg, fromFile);
56253
+ if (entryFilePath === null) return lookupFailed(derivedPkg);
56254
+ const packageDir = findPackageRoot(entryFilePath, derivedPkg);
56255
+ if (packageDir === null) return lookupFailed(derivedPkg);
56256
+ let raw;
56257
+ try {
56258
+ raw = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(packageDir, "rozie-manifest.json"), "utf-8"));
56259
+ } catch {
56260
+ return lookupFailed(derivedPkg);
56261
+ }
56262
+ return parseManifest(raw, { packageName: derivedPkg });
56263
+ }
56264
+ //#endregion
55885
56265
  //#region ../core/src/ir/threadParamTypes.ts
55886
56266
  /**
55887
56267
  * Recursive template walker: visit every TemplateNode in source order.
@@ -55966,9 +56346,11 @@ function bodyContainsReprojection(body) {
55966
56346
  * the cache reverse-dep edge + resolver `fromFile` argument)
55967
56347
  * @param cache - per-compiler-instance IRCache (D-01)
55968
56348
  * @param resolver - per-compiler-instance ProducerResolver (D-02 / D-12)
56349
+ * @param target - the per-call compile target (Phase 75 Plan 02 —
56350
+ * selects the derived per-target package for a PUBLISHED specifier; D-09)
55969
56351
  * @param diagnostics - accumulator
55970
56352
  */
55971
- function threadParamTypes(ir, consumerPath, cache, resolver, diagnostics) {
56353
+ function threadParamTypes(ir, consumerPath, cache, resolver, target, diagnostics) {
55972
56354
  const declaredSlotNames = new Set(ir.slots.map((s) => s.name));
55973
56355
  const { reprojections } = collectSlotInvocationsByContext(ir.template);
55974
56356
  for (const reproj of reprojections) if (!declaredSlotNames.has(reproj.slotName)) diagnostics.push({
@@ -55988,6 +56370,26 @@ function threadParamTypes(ir, consumerPath, cache, resolver, diagnostics) {
55988
56370
  if (node.tagKind === "self") {
55989
56371
  producerSlots = ir.slots;
55990
56372
  producerProps = ir.props;
56373
+ } else if (isPublishedSpecifier$1(node.componentRef.importPath)) {
56374
+ const { surface, error } = resolveManifestProducer({
56375
+ specifier: node.componentRef.importPath,
56376
+ target,
56377
+ fromFile: consumerPath,
56378
+ resolver
56379
+ });
56380
+ if (error !== null) {
56381
+ diagnostics.push({
56382
+ code: error.code,
56383
+ severity: "error",
56384
+ message: error.message,
56385
+ loc: node.componentRef.sourceLoc,
56386
+ hint: "Verify the per-target package is installed as a dependency and ships a compatible rozie-manifest.json."
56387
+ });
56388
+ return;
56389
+ }
56390
+ if (surface === null) return;
56391
+ producerSlots = surface.slots;
56392
+ producerProps = surface.props;
55991
56393
  } else {
55992
56394
  const resolvedPath = resolver.resolveProducerPath(node.componentRef.importPath, consumerPath);
55993
56395
  if (resolvedPath === null) {
@@ -56192,9 +56594,11 @@ function walkTemplate(node, visit) {
56192
56594
  * @param cache - per-compiler-instance IRCache (D-01) — shared with
56193
56595
  * threadParamTypes so producers parsed once during threading are reused here
56194
56596
  * @param resolver - per-compiler-instance ProducerResolver (D-02 / D-12)
56597
+ * @param target - the per-call compile target (Phase 75 Plan 02 —
56598
+ * selects the derived per-target package for a PUBLISHED specifier; D-09)
56195
56599
  * @param diagnostics - accumulator (mutated in place; ROZ949/950/951/945 pushed)
56196
56600
  */
56197
- function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics) {
56601
+ function validateTwoWayBindings(ir, consumerPath, cache, resolver, target, diagnostics) {
56198
56602
  walkTemplate(ir.template, (node) => {
56199
56603
  if (node.type !== "TemplateElement") return;
56200
56604
  const twoWayAttrs = node.attributes.filter((a) => a.kind === "twoWayBinding");
@@ -56209,6 +56613,27 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56209
56613
  return producerProps;
56210
56614
  }
56211
56615
  if (node.tagKind !== "component" || !node.componentRef) return null;
56616
+ if (isPublishedSpecifier$1(node.componentRef.importPath)) {
56617
+ const { surface, error } = resolveManifestProducer({
56618
+ specifier: node.componentRef.importPath,
56619
+ target,
56620
+ fromFile: consumerPath,
56621
+ resolver
56622
+ });
56623
+ if (error !== null) {
56624
+ diagnostics.push({
56625
+ code: error.code,
56626
+ severity: "error",
56627
+ message: error.message,
56628
+ loc: node.componentRef.sourceLoc,
56629
+ hint: "Verify the per-target package is installed as a dependency and ships a compatible rozie-manifest.json."
56630
+ });
56631
+ return null;
56632
+ }
56633
+ if (surface === null) return null;
56634
+ producerProps = surface.props;
56635
+ return producerProps;
56636
+ }
56212
56637
  const resolvedPath = resolver.resolveProducerPath(node.componentRef.importPath, consumerPath);
56213
56638
  if (resolvedPath === null) {
56214
56639
  diagnostics.push({
@@ -56263,6 +56688,7 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56263
56688
  if (producerProp === void 0) continue;
56264
56689
  if (!producerProp.isModel) {
56265
56690
  const producerImportPath = node.tagKind === "self" ? "(self-reference)" : node.componentRef?.importPath ?? "(unknown)";
56691
+ const relatedLoc = producerProp.sourceLoc ?? node.componentRef?.sourceLoc ?? attr.sourceLoc;
56266
56692
  diagnostics.push({
56267
56693
  code: RozieErrorCode.TWO_WAY_PROP_NOT_MODEL,
56268
56694
  severity: "error",
@@ -56271,7 +56697,7 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56271
56697
  hint: `Add 'model: true' to the '${attr.name}' prop declaration on ${producerImportPath}, or pass a one-way value via :${attr.name}="…" instead.`,
56272
56698
  related: [{
56273
56699
  message: `'${attr.name}' declared here without model: true`,
56274
- loc: producerProp.sourceLoc
56700
+ loc: relatedLoc
56275
56701
  }]
56276
56702
  });
56277
56703
  continue;
@@ -56582,10 +57008,37 @@ const TARGET_EXT_MAP = {
56582
57008
  lit: ""
56583
57009
  };
56584
57010
  const ROZIE_EXT$1 = ".rozie";
57011
+ /** Tsconfig-alias-shaped prefix (empty npm scope, e.g. `@/components/Modal.rozie`). */
57012
+ const TSCONFIG_ALIAS_PREFIX = "@/";
57013
+ /**
57014
+ * True iff `importPath` is a PUBLISHED cross-package `<components>`
57015
+ * specifier (D-08 shape): ends in `.rozie`, is neither a relative/absolute
57016
+ * path nor a tsconfig `paths`-alias-shaped specifier. Mirrors
57017
+ * `resolveManifestProducer.ts`'s `isPublishedSpecifier` (kept duplicated,
57018
+ * not imported, so this module stays dependency-free — both are tiny, pure,
57019
+ * syntactic classifiers over the same D-08 specifier shape and must be kept
57020
+ * in lockstep if that shape ever changes).
57021
+ *
57022
+ * Exported so callers that do NOT already route an import path through
57023
+ * `rewriteRozieImport` (e.g. the Lit emitter's side-effect/type-only
57024
+ * composed-component imports, which deliberately leave a LOCAL `.rozie`
57025
+ * specifier verbatim for the unplugin-consumer-coexist flow) can still
57026
+ * gate a published-specifier rewrite without duplicating this check.
57027
+ */
57028
+ function isPublishedSpecifier(importPath) {
57029
+ if (!importPath.endsWith(ROZIE_EXT$1)) return false;
57030
+ if (importPath.startsWith(".") || importPath.startsWith("/")) return false;
57031
+ if (importPath.startsWith(TSCONFIG_ALIAS_PREFIX)) return false;
57032
+ return true;
57033
+ }
56585
57034
  function rewriteRozieImport(importPath, target) {
56586
57035
  if (!importPath.endsWith(ROZIE_EXT$1)) return importPath;
56587
57036
  const targetExt = TARGET_EXT_MAP[target];
56588
57037
  if (targetExt === void 0) throw new Error(`rewriteRozieImport: unknown target '${target}' — expected one of vue|react|svelte|angular|solid|lit`);
57038
+ if (isPublishedSpecifier(importPath)) {
57039
+ const lastSlash = importPath.lastIndexOf("/");
57040
+ if (lastSlash > 0) return `${importPath.slice(0, lastSlash)}-${target}`;
57041
+ }
56589
57042
  return importPath.slice(0, -6) + targetExt;
56590
57043
  }
56591
57044
  //#endregion
@@ -56977,7 +57430,6 @@ function deconflictVueRefSuffix(ir, topLevelBindingNames) {
56977
57430
  }
56978
57431
  //#endregion
56979
57432
  //#region ../core/src/codegen/buildPropJsdoc.ts
56980
- var import_lib = /* @__PURE__ */ __toESM(require_lib$6(), 1);
56981
57433
  /**
56982
57434
  * Neutralize the JSDoc comment terminator inside an author-controlled string so
56983
57435
  * it cannot prematurely close the emitted comment block (T-58-04). Replaces each
@@ -57869,6 +58321,22 @@ function renderComputedArrow$3(body, cast) {
57869
58321
  * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
57870
58322
  * mirrored trailing copy spaces correctly.
57871
58323
  */
58324
+ /**
58325
+ * Quick task 260714-orv — compare two Babel comment nodes by SOURCE POSITION
58326
+ * (type + starting line/column) rather than object reference. A comment
58327
+ * shared between two adjacent statements' trailing/leading arrays sometimes
58328
+ * IS the exact same object, but an earlier pipeline pass (hoistModuleLet /
58329
+ * rewriteRozieIdentifiers / etc.) may clone an individual statement, minting
58330
+ * a content-identical comment object at a different reference. Two distinct,
58331
+ * independently-authored comments can never share a starting source
58332
+ * position, so this is a safe substitute for `===` this late in the
58333
+ * pipeline.
58334
+ */
58335
+ function isSameSourceComment$1(a, b) {
58336
+ if (a === b) return true;
58337
+ if (!a.loc || !b.loc) return false;
58338
+ return a.type === b.type && a.loc.start.line === b.loc.start.line && a.loc.start.column === b.loc.start.column;
58339
+ }
57872
58340
  function mirrorSpliceBoundaryComments$2(stmts) {
57873
58341
  for (let i = 1; i < stmts.length; i++) {
57874
58342
  const cur = stmts[i];
@@ -57877,7 +58345,15 @@ function mirrorSpliceBoundaryComments$2(stmts) {
57877
58345
  const prevExtra = prev.extra;
57878
58346
  const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
57879
58347
  const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
57880
- if (!curSpliced && !prevSpliced) continue;
58348
+ if (!curSpliced && !prevSpliced) {
58349
+ const prevTrail = prev.trailingComments;
58350
+ const curLead = cur.leadingComments;
58351
+ if (prevTrail && prevTrail.length > 0 && curLead && curLead.length > 0) {
58352
+ const deduped = prevTrail.filter((c) => !curLead.some((lc) => isSameSourceComment$1(c, lc)));
58353
+ prev.trailingComments = deduped.length > 0 ? deduped : null;
58354
+ }
58355
+ continue;
58356
+ }
57881
58357
  const lead = cur.leadingComments;
57882
58358
  const prevTrail = prev.trailingComments;
57883
58359
  if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
@@ -59988,7 +60464,61 @@ function emitElement$5(node, ctx) {
59988
60464
  * (`<template #header="{ close }">…</template>`); default-shorthand
59989
60465
  * (`{ name: '' }`) emits as bare children inside the component tag.
59990
60466
  */
60467
+ /**
60468
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
60469
+ * teleport. Wraps the element in Vue's NATIVE `<Teleport :to :disabled>`
60470
+ * component — authors cannot write `<Teleport>` directly (ROZ926 rejects
60471
+ * it), but the EMITTER may synthesize it as the lowering target for
60472
+ * `r-portal` (ROZ926 gates author input, not emit output). `<Teleport>`
60473
+ * tolerates a `v-if`/`v-for` child, so `extraDirective` (the r-if/r-for
60474
+ * directive the parent conditional/loop walker wants to inject) is applied
60475
+ * to the INNER element, not the `<Teleport>` wrapper — the wrapper is
60476
+ * unconditional; when `disabled` the child's own `v-if` still governs
60477
+ * whether anything renders at all.
60478
+ *
60479
+ * `:disabled="!(<expr>)"` — a falsy container renders in place (Vue's
60480
+ * Teleport skips target resolution/warnings entirely when disabled; see
60481
+ * `resolveTarget`/`mountToTarget` in @vue/runtime-core — no console warning,
60482
+ * no throw, for a falsy `to` when `disabled` is true).
60483
+ *
60484
+ * Finding 4 (R2) — the container expression is HOISTED into a single Vue
60485
+ * `computed` (the same D-04 hoist pattern `r-match` uses) instead of being
60486
+ * spliced verbatim into both `:to` and `:disabled`. This fixes three
60487
+ * defects at once:
60488
+ * 1. SSR guard — the computed body wraps the expression in the SAME
60489
+ * `typeof document === 'undefined' ? null : (...)` guard the React and
60490
+ * Solid targets emit, so a container expression that touches `document`
60491
+ * (e.g. `document.querySelector(...)`) is null on the server rather
60492
+ * than throwing; a null container makes `:disabled` truthy → renders
60493
+ * in place (parity with React's ternary / Solid's <Show> fallback).
60494
+ * 2. single evaluation — a Vue computed caches, so a side-effecting
60495
+ * selector runs once per render, not twice (`:to` + `:disabled`).
60496
+ * 3. escaping — the raw expression now lives in the script (where
60497
+ * @babel/generator escapes JS strings), and `:to`/`:disabled` bind a
60498
+ * bare computed identifier, so an expression containing a `"` can no
60499
+ * longer break the double-quoted attribute (obviating a per-attribute
60500
+ * escapeAttrValue call at the interpolation site).
60501
+ */
60502
+ function emitPortalElement$1(node, extraDirective, ctx) {
60503
+ const { expression } = node.portalTo;
60504
+ if (ctx.elementPortalFlag) ctx.elementPortalFlag.seen = true;
60505
+ const containerCode = rewriteTemplateExpression$5(expression, ctx.ir);
60506
+ const hoistName = `__roziePortalTo${ctx.injectionCounter.next++}`;
60507
+ ctx.scriptInjections.push({
60508
+ wrapName: hoistName,
60509
+ import: {
60510
+ from: "vue",
60511
+ name: "computed"
60512
+ },
60513
+ decl: `const ${hoistName} = computed(() => (typeof document === 'undefined' ? null : (${containerCode})));`
60514
+ });
60515
+ return `<Teleport :to="${hoistName}" :disabled="!${hoistName}">${emitElementWithExtraDirectiveInner(node, extraDirective, ctx)}</Teleport>`;
60516
+ }
59991
60517
  function emitElementWithExtraDirective(origNode, extraDirective, ctx) {
60518
+ if (origNode.portalTo) return emitPortalElement$1(origNode, extraDirective, ctx);
60519
+ return emitElementWithExtraDirectiveInner(origNode, extraDirective, ctx);
60520
+ }
60521
+ function emitElementWithExtraDirectiveInner(origNode, extraDirective, ctx) {
59992
60522
  const node = stripKeynavCommitEvent$5(origNode);
59993
60523
  const rHtml = findRHtml$2(node.attributes);
59994
60524
  const attrText = emitMergedAttributes(rHtml ? node.attributes.filter((a) => !(a.kind === "binding" && a.name === "r-html")) : node.attributes, {
@@ -65085,6 +65615,20 @@ function genBlockInner(block, indent) {
65085
65615
  }).join("\n");
65086
65616
  }
65087
65617
  /**
65618
+ * Quick task 260714-orv — render hoisted user imports in ONE
65619
+ * @babel/generator pass so a comment shared between two adjacent imports
65620
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
65621
+ * later import's `leadingComments`) prints exactly once. Mirrors
65622
+ * `genBlockInner`'s single-pass precedent — generating imports one at a time
65623
+ * (`nodes.map(genCode).join`) gives each import its OWN comment-dedup set,
65624
+ * doubling any shared comment. `t.program(nodes)` prints only its body
65625
+ * statements (no wrapping braces/`File` boilerplate), so non-comment cases
65626
+ * stay byte-identical.
65627
+ */
65628
+ function genImportsBlock$2(nodes) {
65629
+ return generate$21(_babel_types.program(nodes), GEN_OPTS$19).code;
65630
+ }
65631
+ /**
65088
65632
  * Phase 14.1 follow-up — does any element in the IR template carry a
65089
65633
  * `spreadBinding` attribute? The Plan 14-05 gate at `emitScript` only
65090
65634
  * synthesises `const attrs = ...` (the source for the `$attrs` rewrite at
@@ -65936,7 +66480,7 @@ function emitScript$4(ir, collectors, opts = {}) {
65936
66480
  const cloned = cloneScriptProgram$4(ir.setupBody.scriptProgram);
65937
66481
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts } = partitionUserImports$3(cloned);
65938
66482
  cloned.program.body = bodyStmts;
65939
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$7(imp)).join("\n") + "\n" : "";
66483
+ const userImports = userImportNodes.length > 0 ? genImportsBlock$2(userImportNodes) + "\n" : "";
65940
66484
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => genCode$7(decl));
65941
66485
  deconflictDeclareThenAssignRef(cloned, ir);
65942
66486
  const hoistResult = hoistModuleLet(cloned, ir);
@@ -69320,7 +69864,42 @@ function scopeAttrForElement$1(node, ctx) {
69320
69864
  * declarations are hoisted within their containing scope). Both emit the tag
69321
69865
  * verbatim PascalCase below; no template AST rewrite needed.
69322
69866
  */
69867
+ /**
69868
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
69869
+ * teleport. Wraps the normal element emit (`emitElementInner`) in a
69870
+ * `container ? createPortal(tree, container) : tree` check, using React's
69871
+ * native `createPortal` (react-dom) — NEVER the P33 `emitPortals.ts`
69872
+ * createRoot-into-container machinery (that is the INVERSE direction: slot-
69873
+ * content mounted INTO an engine-owned container, not a component's own
69874
+ * subtree relocated OUT).
69875
+ *
69876
+ * SSR guard: the container expression is evaluated ONLY when `document`
69877
+ * exists — some author expressions call `document.querySelector` internally
69878
+ * (see `resolveAppendTo`/`resolveTo` in the authoring convention), so simply
69879
+ * checking the RESULT for null is not enough; the whole resolution must be
69880
+ * skipped on the server. Falsy/null container falls back to rendering the
69881
+ * subtree in place — this is what makes `appendTo:false` byte-behavior-
69882
+ * identical to no directive at emit time (the lower-time field is always
69883
+ * present per Task 1; this is the runtime decision point).
69884
+ *
69885
+ * Returns a `{...}` JSX-expression-container mustache — valid directly in
69886
+ * JSX children position, and `renderBranchBody` (emitConditional.ts) strips
69887
+ * the outer braces via `stripBalancedMustache` when this is a single-child
69888
+ * r-if branch body, so `r-portal` + `r-if` on the same element compose for
69889
+ * free with zero special-casing in the conditional emitter.
69890
+ */
69891
+ function emitPortalElement(node, ctx) {
69892
+ const { expression } = node.portalTo;
69893
+ if (ctx.elementPortalFlag) ctx.elementPortalFlag.seen = true;
69894
+ const containerCode = rewriteTemplateExpression$4(expression, ctx.ir);
69895
+ const treeJsx = emitElementInner$4(node, ctx);
69896
+ return `{(() => { const __rozieContainer = typeof document === 'undefined' ? null : (${containerCode}); return __rozieContainer ? createPortal(${treeJsx}, __rozieContainer) : (${treeJsx}); })()}`;
69897
+ }
69323
69898
  function emitElement$4(origNode, ctx) {
69899
+ if (origNode.portalTo) return emitPortalElement(origNode, ctx);
69900
+ return emitElementInner$4(origNode, ctx);
69901
+ }
69902
+ function emitElementInner$4(origNode, ctx) {
69324
69903
  let node = stripKeynavCommitEvent$4(origNode);
69325
69904
  const pendingKey = ctx.pendingKey ?? null;
69326
69905
  const childCtx = {
@@ -69870,9 +70449,11 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69870
70449
  scriptInjections: [],
69871
70450
  slotPropFields: slotResult.slotPropFields,
69872
70451
  slotCtxInterfaces: slotResult.slotCtxInterfaces,
69873
- diagnostics: []
70452
+ diagnostics: [],
70453
+ hasElementPortal: false
69874
70454
  };
69875
70455
  const keynav = resolveKeynavPlan$4(ir);
70456
+ const elementPortalFlag = { seen: false };
69876
70457
  const ctx = {
69877
70458
  ir,
69878
70459
  collectors,
@@ -69881,7 +70462,8 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69881
70462
  scriptInjections,
69882
70463
  injectionCounter: { next: 0 },
69883
70464
  ...opts.scopeAttr !== void 0 ? { scopeAttr: opts.scopeAttr } : {},
69884
- keynav
70465
+ keynav,
70466
+ elementPortalFlag
69885
70467
  };
69886
70468
  const jsx = emitNode$4(ir.template, ctx);
69887
70469
  if (keynav !== null) scriptInjections.push(...buildKeynavScriptInjections$2(keynav, ir, collectors));
@@ -69890,7 +70472,8 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69890
70472
  scriptInjections,
69891
70473
  slotPropFields: slotResult.slotPropFields,
69892
70474
  slotCtxInterfaces: slotResult.slotCtxInterfaces,
69893
- diagnostics
70475
+ diagnostics,
70476
+ hasElementPortal: elementPortalFlag.seen
69894
70477
  };
69895
70478
  }
69896
70479
  //#endregion
@@ -73810,11 +74393,11 @@ function emitReact(ir, opts = {}) {
73810
74393
  runtime: runtimeImports,
73811
74394
  filename: opts.filename
73812
74395
  }, scriptOpts);
73813
- const portalImport = hasPortals ? "import { createRoot, type Root } from 'react-dom/client';\nimport { flushSync } from 'react-dom';\n" : "";
73814
74396
  const tmpl = emitTemplate$4(ir, {
73815
74397
  react: reactImports,
73816
74398
  runtime: runtimeImports
73817
74399
  }, registry, { scopeAttr });
74400
+ const portalImport = (hasPortals ? "import { createRoot, type Root } from 'react-dom/client';\nimport { flushSync } from 'react-dom';\n" : "") + (tmpl.hasElementPortal ? "import { createPortal } from 'react-dom';\n" : "");
73818
74401
  const listeners = emitListeners$4(ir, {
73819
74402
  react: reactImports,
73820
74403
  runtime: runtimeImports
@@ -74015,11 +74598,11 @@ function renderPropType(ann) {
74015
74598
  case "Boolean": return "boolean";
74016
74599
  case "Array": return "unknown[]";
74017
74600
  case "Object": return "Record<string, unknown>";
74018
- case "Function": return "(...args: unknown[]) => unknown";
74601
+ case "Function": return "(...args: any[]) => any";
74019
74602
  default: return ann.name;
74020
74603
  }
74021
74604
  if (ann.kind === "literal") switch (ann.value) {
74022
- case "function": return "(...args: unknown[]) => unknown";
74605
+ case "function": return "(...args: any[]) => any";
74023
74606
  case "object": return "Record<string, unknown>";
74024
74607
  case "array": return "unknown[]";
74025
74608
  case "string": return "string";
@@ -75305,6 +75888,22 @@ function arrowBody$2(body) {
75305
75888
  * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
75306
75889
  * mirrored trailing copy spaces correctly.
75307
75890
  */
75891
+ /**
75892
+ * Quick task 260714-orv — compare two Babel comment nodes by SOURCE POSITION
75893
+ * (type + starting line/column) rather than object reference. A comment
75894
+ * shared between two adjacent statements' trailing/leading arrays sometimes
75895
+ * IS the exact same object, but an earlier pipeline pass (hoistModuleLet /
75896
+ * rewriteRozieIdentifiers / etc.) may clone an individual statement, minting
75897
+ * a content-identical comment object at a different reference. Two distinct,
75898
+ * independently-authored comments can never share a starting source
75899
+ * position, so this is a safe substitute for `===` this late in the
75900
+ * pipeline.
75901
+ */
75902
+ function isSameSourceComment(a, b) {
75903
+ if (a === b) return true;
75904
+ if (!a.loc || !b.loc) return false;
75905
+ return a.type === b.type && a.loc.start.line === b.loc.start.line && a.loc.start.column === b.loc.start.column;
75906
+ }
75308
75907
  function mirrorSpliceBoundaryComments$1(stmts) {
75309
75908
  for (let i = 1; i < stmts.length; i++) {
75310
75909
  const cur = stmts[i];
@@ -75313,7 +75912,15 @@ function mirrorSpliceBoundaryComments$1(stmts) {
75313
75912
  const prevExtra = prev.extra;
75314
75913
  const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
75315
75914
  const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
75316
- if (!curSpliced && !prevSpliced) continue;
75915
+ if (!curSpliced && !prevSpliced) {
75916
+ const prevTrail = prev.trailingComments;
75917
+ const curLead = cur.leadingComments;
75918
+ if (prevTrail && prevTrail.length > 0 && curLead && curLead.length > 0) {
75919
+ const deduped = prevTrail.filter((c) => !curLead.some((lc) => isSameSourceComment(c, lc)));
75920
+ prev.trailingComments = deduped.length > 0 ? deduped : null;
75921
+ }
75922
+ continue;
75923
+ }
75317
75924
  const lead = cur.leadingComments;
75318
75925
  const prevTrail = prev.trailingComments;
75319
75926
  if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
@@ -77849,6 +78456,11 @@ function emitElementInner$3(origNode, ctx) {
77849
78456
  hasFallthroughSpread = true;
77850
78457
  break;
77851
78458
  }
78459
+ let portalAttr = null;
78460
+ if (node.portalTo) {
78461
+ ctx.runtimeImports.add("roziePortal");
78462
+ portalAttr = `use:roziePortal={${rewriteTemplateExpression$3(node.portalTo.expression, ctx.ir, ctxRenames(ctx))}}`;
78463
+ }
77852
78464
  const attrText = emitAttributes$2(node.attributes, {
77853
78465
  ir: ctx.ir,
77854
78466
  elementTagKind: node.tagKind,
@@ -77877,6 +78489,7 @@ function emitElementInner$3(origNode, ctx) {
77877
78489
  if (eventText) partsHead.push(eventText);
77878
78490
  for (const sp of spreadTexts) partsHead.push(sp);
77879
78491
  for (const ka of keynavAttrs) partsHead.push(ka);
78492
+ if (portalAttr) partsHead.push(portalAttr);
77880
78493
  if (ctx.scopeAttr.length > 0) partsHead.push(ctx.scopeAttr);
77881
78494
  const head = partsHead.length > 0 ? " " + partsHead.join(" ") : "";
77882
78495
  if (rHtml !== null) {
@@ -79608,6 +80221,88 @@ function hoistDoubleReadAccessors(program) {
79608
80221
  }
79609
80222
  visit(program.program);
79610
80223
  }
80224
+ /** True for `$props.X` where X is in `polymorphicModelProps`. */
80225
+ function isPolymorphicModelRead$1(node, polymorphicModelProps) {
80226
+ return _babel_types.isMemberExpression(node) && !node.computed && _babel_types.isIdentifier(node.object) && node.object.name === "$props" && _babel_types.isIdentifier(node.property) && polymorphicModelProps.has(node.property.name);
80227
+ }
80228
+ /**
80229
+ * When `test` is `typeof $props.X === '<lit>'` (or `!==`) or `'<lit>' in
80230
+ * $props.X`, for X in `polymorphicModelProps`, returns X. Otherwise null.
80231
+ */
80232
+ function matchesPolymorphicModelGuard$1(test, polymorphicModelProps) {
80233
+ if (_babel_types.isLogicalExpression(test)) return matchesPolymorphicModelGuard$1(test.left, polymorphicModelProps) ?? matchesPolymorphicModelGuard$1(test.right, polymorphicModelProps);
80234
+ if (!_babel_types.isBinaryExpression(test)) return null;
80235
+ const { operator, left, right } = test;
80236
+ if (operator === "===" || operator === "!==") {
80237
+ if (_babel_types.isUnaryExpression(left) && left.operator === "typeof" && isPolymorphicModelRead$1(left.argument, polymorphicModelProps) && _babel_types.isStringLiteral(right)) return left.argument.property.name;
80238
+ if (_babel_types.isUnaryExpression(right) && right.operator === "typeof" && isPolymorphicModelRead$1(right.argument, polymorphicModelProps) && _babel_types.isStringLiteral(left)) return right.argument.property.name;
80239
+ return null;
80240
+ }
80241
+ if (operator === "in" && isPolymorphicModelRead$1(right, polymorphicModelProps)) return right.property.name;
80242
+ return null;
80243
+ }
80244
+ /**
80245
+ * Quick task 260711-v2l (project_angular_typeof_narrow_gap) — port of Solid's
80246
+ * `hoistPolymorphicModelGuards` (emitter-hardening backlog item #11,
80247
+ * `project_solid_polymorphic_model_typeof_narrow_gap`, 73-02 Task 2) to the
80248
+ * Angular target. `$props.X` (a union or `unknown`-typed `model: true` prop)
80249
+ * lowers to an Angular signal accessor CALL (`this.X()`); TS does not narrow a
80250
+ * `typeof`/`in` guard across two SEPARATE calls to that accessor the way it
80251
+ * narrows a plain variable/property read. When a `ConditionalExpression`
80252
+ * guards on `typeof $props.X === '<lit>'` / `'<lit>' in $props.X` (X a
80253
+ * polymorphic/unknown model prop) AND re-reads `$props.X` again inside the
80254
+ * conditional, hoist ONE local binding before the guard and route every
80255
+ * `$props.X` read inside the conditional through it — mirroring the hand-
80256
+ * authored `const v = $props.value; return typeof v === 'string' ? v : ''`
80257
+ * workaround this replaces (DatePicker.rozie, commit bf3766b5).
80258
+ *
80259
+ * Gated to two known-safe shapes so this never mis-hoists into the wrong scope:
80260
+ * 1. the conditional IS an arrow function's own concise body — converted to
80261
+ * a block body wrapping `const v = ...; return <conditional>;`
80262
+ * 2. the conditional sits inside a statement that lives in a statement LIST
80263
+ * (block body, switch-case consequent, program body) — including
80264
+ * statements nested inside control-flow blocks (`if`/`for`/`while`/
80265
+ * `switch`/`try`). The local is inserted right before that statement, in
80266
+ * the same block as the guarded reads (Spike-012 BUG-4).
80267
+ * A conditional not in either shape (e.g. a bare single-statement `if (x)
80268
+ * return …;` with no block) is left alone (falsify-to-no-op, never a wrong fix).
80269
+ *
80270
+ * Runs on the raw (pre-$props-rewrite) Program, before the main
80271
+ * `rewriteRozieIdentifiers` traversal — the injected `$props.X` reference is
80272
+ * left for that later pass to lower to the normal `this.X()` accessor call
80273
+ * (identical downstream lowering to the DatePicker hand-authored workaround
80274
+ * this replaces).
80275
+ */
80276
+ function hoistPolymorphicModelGuards$1(cloned, polymorphicModelProps) {
80277
+ if (polymorphicModelProps.size === 0) return;
80278
+ traverse$8(cloned, { ConditionalExpression(path) {
80279
+ const propName = matchesPolymorphicModelGuard$1(path.node.test, polymorphicModelProps);
80280
+ if (!propName) return;
80281
+ let occurrences = 0;
80282
+ path.traverse({ MemberExpression(inner) {
80283
+ if (isPolymorphicModelRead$1(inner.node, polymorphicModelProps) && inner.node.property.name === propName) occurrences++;
80284
+ } });
80285
+ if (occurrences < 2) return;
80286
+ const parentPath = path.parentPath;
80287
+ const isConciseArrowBody = parentPath.isArrowFunctionExpression() && parentPath.node.body === path.node;
80288
+ const stmtPath = path.getStatementParent();
80289
+ const canInsertBeforeStmt = !!stmtPath && stmtPath.inList;
80290
+ if (!isConciseArrowBody && !canInsertBeforeStmt) return;
80291
+ const localName = path.scope.hasBinding("v") ? path.scope.generateUidIdentifier("v").name : "v";
80292
+ path.traverse({ MemberExpression(inner) {
80293
+ if (isPolymorphicModelRead$1(inner.node, polymorphicModelProps) && inner.node.property.name === propName) {
80294
+ inner.replaceWith(_babel_types.identifier(localName));
80295
+ inner.skip();
80296
+ }
80297
+ } });
80298
+ const varDecl = _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.identifier(localName), _babel_types.memberExpression(_babel_types.identifier("$props"), _babel_types.identifier(propName)))]);
80299
+ if (isConciseArrowBody) {
80300
+ parentPath.node.body = _babel_types.blockStatement([varDecl, _babel_types.returnStatement(path.node)]);
80301
+ return;
80302
+ }
80303
+ stmtPath.insertBefore(varDecl);
80304
+ } });
80305
+ }
79611
80306
  /**
79612
80307
  * Rewrite Rozie magic-accessor identifiers in-place on a cloned Program.
79613
80308
  *
@@ -81825,6 +82520,19 @@ function genCode$3(node) {
81825
82520
  return generate$9(node, GEN_OPTS$8).code;
81826
82521
  }
81827
82522
  /**
82523
+ * Quick task 260714-orv — render hoisted user imports in ONE
82524
+ * @babel/generator pass so a comment shared between two adjacent imports
82525
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
82526
+ * later import's `leadingComments`) prints exactly once. Generating imports
82527
+ * one at a time (`nodes.map(genCode).join`) gives each import its OWN
82528
+ * comment-dedup set, doubling any shared comment. `t.program(nodes)` prints
82529
+ * only its body statements (no wrapping braces/`File` boilerplate), so
82530
+ * non-comment cases stay byte-identical.
82531
+ */
82532
+ function genImportsBlock$1(nodes) {
82533
+ return generate$9(_babel_types.program(nodes), GEN_OPTS$8).code;
82534
+ }
82535
+ /**
81828
82536
  * Emit `() => body` for an Expression or BlockStatement body.
81829
82537
  *
81830
82538
  * Building the arrow as a Babel node (rather than string-templating
@@ -81907,7 +82615,7 @@ function renderType$2(ann) {
81907
82615
  case "Boolean": return "boolean";
81908
82616
  case "Array": return "any[]";
81909
82617
  case "Object": return "Record<string, any>";
81910
- case "Function": return "(...args: unknown[]) => unknown";
82618
+ case "Function": return "(...args: any[]) => any";
81911
82619
  default: return ann.name;
81912
82620
  }
81913
82621
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -81917,7 +82625,7 @@ function renderType$2(ann) {
81917
82625
  if (ann.kind === "literal") {
81918
82626
  if (ann.value === "array") return "any[]";
81919
82627
  if (ann.value === "object") return "Record<string, any>";
81920
- if (ann.value === "function") return "(...args: unknown[]) => unknown";
82628
+ if (ann.value === "function") return "(...args: any[]) => any";
81921
82629
  return ann.value;
81922
82630
  }
81923
82631
  return "unknown";
@@ -82232,8 +82940,9 @@ function emitScript$2(ir, opts = {}) {
82232
82940
  deconflictReservedImportBindings(cloned, new Set([...ir.props.map((p) => p.name), ...reservedClassMembers("angular", { singleModel: importSingleModel })]));
82233
82941
  const { userImports: userImportNodes, hoistedTypeDecls, bodyStmts, valueImportNames } = partitionUserImports$2(cloned);
82234
82942
  cloned.program.body = bodyStmts;
82235
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$3(imp)).join("\n") + "\n" : "";
82943
+ const userImports = userImportNodes.length > 0 ? genImportsBlock$1(userImportNodes) + "\n" : "";
82236
82944
  normalizeModelAccessor$2(cloned);
82945
+ hoistPolymorphicModelGuards$1(cloned, new Set(ir.props.filter((p) => p.isModel && (p.typeAnnotation.kind === "union" || renderType$2(p.typeAnnotation) === "unknown")).map((p) => p.name)));
82237
82946
  hoistDoubleReadAccessors(cloned);
82238
82947
  const lifecyclePairing = pairClonedLifecycle(cloned, ir);
82239
82948
  const emitsWithPayload = collectEmitsWithPayload(cloned);
@@ -82871,6 +83580,42 @@ function shouldWrapAttrBinding$1(name, expr, ctx, elementTagName) {
82871
83580
  return true;
82872
83581
  }
82873
83582
  /**
83583
+ * Quick task 260711-tgk — Angular's `[style]=` (`ɵɵstyleMap`) parses the bound
83584
+ * value into a semicolon-delimited key:value map. Its parser corrupts on an
83585
+ * EMPTY declaration (`;;` — e.g. `pinStyle(colId) + ';' + pad` when
83586
+ * `pinStyle` already ends in `;`), silently DROPPING the declaration that
83587
+ * follows the empty segment. The other 5 targets apply a `:style` STRING
83588
+ * value via the browser's own (tolerant) CSS parser — Angular is the lone
83589
+ * outlier because `[style]=` binds through `ɵɵstyleMap`, not `setAttribute`.
83590
+ *
83591
+ * Fix (see the call site in emitSingleAttr's binding branch): when a
83592
+ * `:style` binding's expression is PROVABLY a string at compile time, emit
83593
+ * `[attr.style]="<expr>"` instead of `[style]="<expr>"`. `[attr.style]`
83594
+ * compiles to `ɵɵattribute` → `setAttribute('style', <string>)`, which the
83595
+ * browser parses tolerantly — matching the other 5 targets' behavior. This
83596
+ * is only reachable for the element's SOLE style contributor: emitAttributes'
83597
+ * `counts.get('style') > 1` merge path (`[ngStyle]`) always intercepts
83598
+ * multi-source style bindings (a co-existing static `style=` or a second
83599
+ * `:style`) via its own `continue`, before emitSingleAttr is ever called for
83600
+ * a style attribute in that case.
83601
+ *
83602
+ * Conservative by design: a false NEGATIVE (returning false for a value that
83603
+ * IS always a string) just keeps the current, already-correct-for-non-`;;`
83604
+ * `[style]=` behavior. A false POSITIVE (returning true for a value that can
83605
+ * be an object) would break object-valued styles — so every branch below is
83606
+ * additive-only and defaults to `false`.
83607
+ */
83608
+ function isProvablyStringStyleExpression(expr) {
83609
+ if (_babel_types.isStringLiteral(expr)) return true;
83610
+ if (_babel_types.isTemplateLiteral(expr)) return true;
83611
+ if (_babel_types.isBinaryExpression(expr) && expr.operator === "+") {
83612
+ if (!_babel_types.isExpression(expr.left)) return false;
83613
+ return isProvablyStringStyleExpression(expr.left) || isProvablyStringStyleExpression(expr.right);
83614
+ }
83615
+ if (_babel_types.isConditionalExpression(expr)) return isProvablyStringStyleExpression(expr.consequent) && isProvablyStringStyleExpression(expr.alternate);
83616
+ return false;
83617
+ }
83618
+ /**
82874
83619
  * Plan 14-05 R6 — keys that must never reach the emitted object from an
82875
83620
  * author-controlled `r-bind` LITERAL. Mirrors the React/Solid/Vue/Svelte
82876
83621
  * `FORBIDDEN_SPREAD_KEYS` set + the Phase 02 `collectPropDecls` write-time
@@ -83280,6 +84025,151 @@ function emitListenerSpread$1(spread, ctx) {
83280
84025
  return `#${refName}`;
83281
84026
  }
83282
84027
  /**
84028
+ * command-palette-portal-overlay phase — the SHARED (once-per-component)
84029
+ * `__roziePortalPlace` helper method. Mirrors `applyAttrsHelperDecl` /
84030
+ * `listenersRendererFieldDecl`'s dedup pattern: pushed onto
84031
+ * `ctx.scriptInjections` at most once regardless of how many `r-portal`
84032
+ * elements the component has.
84033
+ *
84034
+ * A `WeakMap<Element, anchor>` lazily captures each portalled element's
84035
+ * ORIGINAL parent + next-sibling on first placement (so a later falsy
84036
+ * `target` restores it to its exact natural template position — the AOT-
84037
+ * safe, signals-era analog of Vue's `<Teleport :disabled>` / React's
84038
+ * ternary in-place fallback). A truthy `target` moves the element via
84039
+ * native `appendChild` and records it in `__roziePortalMoved` so the
84040
+ * shared `ngOnDestroy` teardown (registered by the per-portal effect, see
84041
+ * `emitPortalDirective`) can remove any STILL-relocated node — Angular's
84042
+ * own view-destroy machinery is not guaranteed to find a node that has
84043
+ * been moved outside its logical view position.
84044
+ */
84045
+ const ROZIE_PORTAL_PLACE_FIELD_NAME = "__roziePortalPlace";
84046
+ const ROZIE_PORTAL_ANCHORS_FIELD_NAME = "__roziePortalAnchors";
84047
+ const ROZIE_PORTAL_MOVED_FIELD_NAME = "__roziePortalMoved";
84048
+ function portalAnchorsFieldDecl() {
84049
+ return `private ${ROZIE_PORTAL_ANCHORS_FIELD_NAME} = new WeakMap<Element, { parent: Node | null; next: Node | null }>();`;
84050
+ }
84051
+ function portalMovedFieldDecl() {
84052
+ return `private ${ROZIE_PORTAL_MOVED_FIELD_NAME} = new Set<Element>();`;
84053
+ }
84054
+ function portalPlaceMethodDecl() {
84055
+ return [
84056
+ `private ${ROZIE_PORTAL_PLACE_FIELD_NAME}(el: Element, target: Element | null | undefined): void {`,
84057
+ ` let anchor = this.${ROZIE_PORTAL_ANCHORS_FIELD_NAME}.get(el);`,
84058
+ ` if (!anchor) {`,
84059
+ ` anchor = { parent: el.parentNode, next: el.nextSibling };`,
84060
+ ` this.${ROZIE_PORTAL_ANCHORS_FIELD_NAME}.set(el, anchor);`,
84061
+ ` }`,
84062
+ ` if (target) {`,
84063
+ ` if (!this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.has(el) && !el.isConnected) return;`,
84064
+ ` if (el.parentNode !== target) {`,
84065
+ ` target.appendChild(el);`,
84066
+ ` }`,
84067
+ ` this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.add(el);`,
84068
+ ` return;`,
84069
+ ` }`,
84070
+ ` if (!this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.has(el)) return;`,
84071
+ ` this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.delete(el);`,
84072
+ ` if (anchor.parent) {`,
84073
+ ` if (anchor.next && anchor.next.parentNode === anchor.parent) {`,
84074
+ ` anchor.parent.insertBefore(el, anchor.next);`,
84075
+ ` } else {`,
84076
+ ` anchor.parent.appendChild(el);`,
84077
+ ` }`,
84078
+ ` }`,
84079
+ `}`
84080
+ ].join("\n");
84081
+ }
84082
+ /**
84083
+ * command-palette-portal-overlay phase — emit the per-element machinery for
84084
+ * ONE `r-portal="<expr>"` element:
84085
+ * - a `#roziePortal_<N>` template-ref attribute (returned for splicing
84086
+ * onto the open tag),
84087
+ * - a `viewChild<ElementRef>('roziePortal_<N>')` private field,
84088
+ * - the SHARED `__roziePortalPlace`/anchors/moved-set fields (once per
84089
+ * component — mirrors `emitSpreadBinding`'s `applyAttrsHelperDecl`
84090
+ * dedup),
84091
+ * - a `private __roziePortal_<N>_effect = effect(() => { ... });` field
84092
+ * initializer that guards `nativeElement` (Pitfall 7 — `viewChild()`
84093
+ * signals return `undefined` until the view is initialized, so a
84094
+ * pre-render effect tick is a safe no-op) and re-runs the placement
84095
+ * whenever the container expression's SIGNAL dependencies change
84096
+ * (the AOT-safe, signals-era analog of a reactive `$watch`).
84097
+ *
84098
+ * AOT-SAFE by construction: no `import.meta.url` (not referenced at all),
84099
+ * no inline template arrow (the container expression is a plain property/
84100
+ * method-call expression spliced into a `effect()` callback BODY — a
84101
+ * function statement, not a template-attribute arrow literal; analogjs AOT
84102
+ * rejects arrows INSIDE `{{ }}`/binding attribute VALUES, not inside a
84103
+ * class-field-initializer's own script-side callback).
84104
+ *
84105
+ * `effect()` in a field initializer is valid Angular injection context
84106
+ * (mirrors `emitSpreadBinding`/`emitListenerSpread`'s identical Pitfall-8
84107
+ * rationale) — no separate `ngAfterViewInit`/constructor wiring is needed,
84108
+ * unlike the r-keynav controller (which needs `viewChild()` to resolve
84109
+ * itself for the FIRST commit-timing-sensitive read; a portal's placement
84110
+ * has no such ordering hazard — `effect()`'s own no-op-until-mounted guard
84111
+ * covers the initial-render case identically to every render after it).
84112
+ */
84113
+ function emitPortalDirective(element, ctx) {
84114
+ const counter = ctx.injectionCounter ?? { next: 0 };
84115
+ const idx = counter.next++;
84116
+ const refName = `roziePortal_${idx}`;
84117
+ const effectFieldName = `__roziePortal_${idx}_effect`;
84118
+ const destroyRegisteredFieldName = `__roziePortal_${idx}_destroyRegistered`;
84119
+ const containerCode = rewriteListenerExpression(element.portalTo.expression, ctx.ir, {
84120
+ collisionRenames: ctx.collisionRenames,
84121
+ classMembers: ctx.classMembers,
84122
+ cvaModelProp: ctx.cvaModelProp,
84123
+ cvaMergeDisabled: ctx.cvaMergeDisabled
84124
+ });
84125
+ if (ctx.scriptInjections !== void 0) {
84126
+ ctx.scriptInjections.push({
84127
+ name: refName,
84128
+ decl: `private ${refName} = viewChild<ElementRef>('${refName}');`
84129
+ });
84130
+ if (!ctx.scriptInjections.some((si) => si.name === ROZIE_PORTAL_ANCHORS_FIELD_NAME)) {
84131
+ ctx.scriptInjections.push({
84132
+ name: ROZIE_PORTAL_ANCHORS_FIELD_NAME,
84133
+ decl: portalAnchorsFieldDecl()
84134
+ });
84135
+ ctx.scriptInjections.push({
84136
+ name: ROZIE_PORTAL_MOVED_FIELD_NAME,
84137
+ decl: portalMovedFieldDecl()
84138
+ });
84139
+ ctx.scriptInjections.push({
84140
+ name: ROZIE_PORTAL_PLACE_FIELD_NAME,
84141
+ decl: portalPlaceMethodDecl()
84142
+ });
84143
+ }
84144
+ ctx.scriptInjections.push({
84145
+ name: destroyRegisteredFieldName,
84146
+ decl: `private ${destroyRegisteredFieldName} = false;`
84147
+ });
84148
+ const effectDecl = [
84149
+ `private ${effectFieldName} = effect(() => {`,
84150
+ ` const el = this.${refName}()?.nativeElement;`,
84151
+ ` if (!el) return;`,
84152
+ ` this.${ROZIE_PORTAL_PLACE_FIELD_NAME}(el, ${containerCode});`,
84153
+ ` if (!this.${destroyRegisteredFieldName}) {`,
84154
+ ` this.${destroyRegisteredFieldName} = true;`,
84155
+ ` this.__rozieDestroyRef.onDestroy(() => {`,
84156
+ ` for (const moved of this.${ROZIE_PORTAL_MOVED_FIELD_NAME}) {`,
84157
+ ` moved.parentNode?.removeChild(moved);`,
84158
+ ` }`,
84159
+ ` });`,
84160
+ ` }`,
84161
+ `});`
84162
+ ].join("\n");
84163
+ ctx.scriptInjections.push({
84164
+ name: effectFieldName,
84165
+ decl: effectDecl
84166
+ });
84167
+ }
84168
+ if (ctx.hasListenerSpread !== void 0) ctx.hasListenerSpread.value = true;
84169
+ if (ctx.needsDestroyRefField !== void 0) ctx.needsDestroyRefField.value = true;
84170
+ return `#${refName}`;
84171
+ }
84172
+ /**
83283
84173
  * Emit a single attribute. Returns null when the attribute should be dropped
83284
84174
  * (e.g., r-html, which gets emitted later as `[innerHTML]="..."` by the
83285
84175
  * element emitter).
@@ -83326,6 +84216,7 @@ function emitSingleAttr(attr, ctx, elementTagName) {
83326
84216
  if (ctx.hasDisplayWrap) ctx.hasDisplayWrap.value = true;
83327
84217
  return `[${bindingName.startsWith("attr.") ? bindingName : `attr.${attr.name}`}]="rozieAttr(${expr})"`;
83328
84218
  }
84219
+ if (attr.name === "style" && isProvablyStringStyleExpression(attr.expression)) return `[attr.style]="${expr}"`;
83329
84220
  return `[${bindingName}]="${expr}"`;
83330
84221
  }
83331
84222
  if (attr.segments.length === 1 && attr.segments[0].kind === "binding") {
@@ -84503,6 +85394,21 @@ function emitElementInner$2(origNode, ctx) {
84503
85394
  if (attrText) partsHead.push(attrText);
84504
85395
  if (eventText) partsHead.push(eventText);
84505
85396
  for (const text of dynamicListenerTexts) partsHead.push(text);
85397
+ if (node.portalTo) {
85398
+ const portalRefText = emitPortalDirective(node, {
85399
+ ir: ctx.ir,
85400
+ collisionRenames: ctx.collisionRenames,
85401
+ loopBindings: ctx.loopBindings,
85402
+ scriptInjections: ctx.scriptInjections,
85403
+ injectionCounter: ctx.injectionCounter,
85404
+ hasListenerSpread: ctx.hasListenerSpread,
85405
+ needsDestroyRefField: ctx.needsDestroyRefField,
85406
+ cvaModelProp: ctx.cvaModelProp,
85407
+ cvaMergeDisabled: ctx.cvaMergeDisabled,
85408
+ classMembers: ctx.classMembers
85409
+ });
85410
+ partsHead.push(portalRefText);
85411
+ }
84506
85412
  const keynav = ctx.keynav ?? null;
84507
85413
  const keynavAttrs = [...keynavRootAttrs$2(keynav, node, ctx.ir), ...keynavItemAttrs$2(keynav, node, ctx.keynavItemIndexAlias ?? null, ctx.ir)];
84508
85414
  for (const a of keynavAttrs) partsHead.push(a);
@@ -86150,7 +87056,7 @@ function renderType$1(ann) {
86150
87056
  case "Boolean": return "boolean";
86151
87057
  case "Array": return "any[]";
86152
87058
  case "Object": return "Record<string, any>";
86153
- case "Function": return "(...args: unknown[]) => unknown";
87059
+ case "Function": return "(...args: any[]) => any";
86154
87060
  default: return ann.name;
86155
87061
  }
86156
87062
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -86160,7 +87066,7 @@ function renderType$1(ann) {
86160
87066
  if (ann.kind === "literal") {
86161
87067
  if (ann.value === "array") return "any[]";
86162
87068
  if (ann.value === "object") return "Record<string, any>";
86163
- if (ann.value === "function") return "(...args: unknown[]) => unknown";
87069
+ if (ann.value === "function") return "(...args: any[]) => any";
86164
87070
  return ann.value;
86165
87071
  }
86166
87072
  return "unknown";
@@ -86289,6 +87195,63 @@ function buildSetterCall$1(varName, operator, rhs) {
86289
87195
  return _babel_types.callExpression(_babel_types.identifier(setterName), [_babel_types.binaryExpression(binOp, _babel_types.callExpression(_babel_types.identifier(varName), []), rhs)]);
86290
87196
  }
86291
87197
  /**
87198
+ * 260712-ig6 Task B — detects the "null-widened prop spliced into a 3rd-party
87199
+ * object-literal call argument" shape: a `$props.<name>` read (where `<name>`
87200
+ * has `default: null`) that ultimately feeds an `ObjectProperty` VALUE inside
87201
+ * an `ObjectExpression` that is (possibly via nested object-literal property
87202
+ * values, e.g. `useEngine(el, { options: { group: $props.group } })`) itself
87203
+ * a DIRECT argument of a `CallExpression` — the mount-time engine-init shape
87204
+ * (`useSortableJS($refs.listEl, { options: { handle: $props.handle, ... } })`).
87205
+ *
87206
+ * Walks UP from the read, tolerating exactly two pass-through shapes on the
87207
+ * way to an `ObjectProperty` value:
87208
+ * - `ConditionalExpression` consequent/alternate (the `group` prop's
87209
+ * `cloneable ? { ... } : $props.group` ternary)
87210
+ * - nested `ObjectExpression` → `ObjectProperty` value chains (an object
87211
+ * literal nested inside another object literal's property value)
87212
+ *
87213
+ * Any OTHER parent shape (binary/logical expression, array literal, bare
87214
+ * statement, non-direct call argument, …) fails the match — this is
87215
+ * intentionally narrow, mirroring Pattern D's `isDirectCallArg` precedent.
87216
+ * Distinct from Pattern D: Pattern D fires on `$refs` DOM-ref reads that are
87217
+ * THEMSELVES a direct call argument; this fires on `$props` reads nested
87218
+ * inside an object-literal call argument.
87219
+ *
87220
+ * `excludeCallees` DISQUALIFIES the match when the terminal call's callee is
87221
+ * one of the emitter's OWN synthesized reactive setters (`set<Data>` /
87222
+ * `set<ModelProp>`, minted by `buildSetterCall` for `$data.x = {...}` /
87223
+ * `$model.x = {...}` writes). Those setter calls are Rozie-internal — the
87224
+ * `{...}` argument feeds a Solid `createSignal` setter, not a 3rd-party lib's
87225
+ * typed options object, so a null-widened prop spliced into one must stay
87226
+ * `null` verbatim (the cross-target `PropDefaultCoercion` conformance probe
87227
+ * asserts `JSON.stringify({ a: null, ... })` renders the literal `"a":null`
87228
+ * substring on every target — `?? undefined` would DROP the key entirely,
87229
+ * a real cross-target behavior regression, not a type-only change).
87230
+ */
87231
+ function isNullWidenedPropObjectLiteralCallArgTarget(path, excludeCallees) {
87232
+ let cur = path;
87233
+ for (;;) {
87234
+ const parent = cur.parentPath;
87235
+ if (!parent) return false;
87236
+ if (parent.isConditionalExpression() && (parent.node.consequent === cur.node || parent.node.alternate === cur.node)) {
87237
+ cur = parent;
87238
+ continue;
87239
+ }
87240
+ if (parent.isObjectProperty() && parent.node.value === cur.node) {
87241
+ const objExpr = parent.parentPath;
87242
+ if (!objExpr || !objExpr.isObjectExpression()) return false;
87243
+ cur = objExpr;
87244
+ continue;
87245
+ }
87246
+ if (parent.isCallExpression() && parent.node.arguments.some((arg) => arg === cur.node)) {
87247
+ const callee = parent.node.callee;
87248
+ if (_babel_types.isIdentifier(callee) && excludeCallees.has(callee.name)) return false;
87249
+ return true;
87250
+ }
87251
+ return false;
87252
+ }
87253
+ }
87254
+ /**
86292
87255
  * Phase 18 (Req 2) — normalize the producer-side two-way-write sigil `$model`
86293
87256
  * to `$props` across a cloned File, in place. See the call-site comment in
86294
87257
  * `rewriteRozieIdentifiers` for the full contract; `$model.X` is model-only and
@@ -86395,7 +87358,7 @@ function hoistPolymorphicModelGuards(cloned, polymorphicModelProps) {
86395
87358
  *
86396
87359
  * @experimental — shape may change before v1.0
86397
87360
  */
86398
- function rewriteRozieExpressionNode(expr, ir) {
87361
+ function rewriteRozieExpressionNode(expr, ir, options) {
86399
87362
  let programBody;
86400
87363
  const isBlock = _babel_types.isBlockStatement(expr);
86401
87364
  if (isBlock) programBody = _babel_types.cloneNode(expr, true, false).body;
@@ -86409,7 +87372,7 @@ function rewriteRozieExpressionNode(expr, ir) {
86409
87372
  sourceType: "module"
86410
87373
  },
86411
87374
  comments: []
86412
- }, ir).rewrittenProgram.program.body;
87375
+ }, ir, options).rewrittenProgram.program.body;
86413
87376
  if (isBlock) return _babel_types.blockStatement(body);
86414
87377
  if (body.length === 1 && _babel_types.isExpressionStatement(body[0])) return body[0].expression;
86415
87378
  const last = body[body.length - 1];
@@ -86420,10 +87383,12 @@ function rewriteRozieExpressionNode(expr, ir) {
86420
87383
  * Full Solid identifier rewrite pass. Replaces all $props/$data/$refs/$emit
86421
87384
  * references with their Solid-idiomatic equivalents.
86422
87385
  */
86423
- function rewriteRozieIdentifiers(cloned, ir) {
87386
+ function rewriteRozieIdentifiers(cloned, ir, options) {
86424
87387
  const diagnostics = [];
87388
+ const nonNullRefCallArgs = options?.nonNullRefCallArgs === true;
86425
87389
  const modelProps = new Set(ir.props.filter((p) => p.isModel).map((p) => p.name));
86426
87390
  const nonModelProps = new Set(ir.props.filter((p) => !p.isModel).map((p) => p.name));
87391
+ const nullWidenedNonModelProps = new Set(ir.props.filter((p) => !p.isModel && _babel_types.isNullLiteral(p.defaultValue)).map((p) => p.name));
86427
87392
  const unknownModelProps = new Set(ir.props.filter((p) => p.isModel && renderType$1(p.typeAnnotation) === "unknown").map((p) => p.name));
86428
87393
  const polymorphicModelProps = new Set(ir.props.filter((p) => p.isModel && (p.typeAnnotation.kind === "union" || renderType$1(p.typeAnnotation) === "unknown")).map((p) => p.name));
86429
87394
  const dataNames = new Set(ir.state.map((s) => s.name));
@@ -86540,6 +87505,12 @@ function rewriteRozieIdentifiers(cloned, ir) {
86540
87505
  return;
86541
87506
  }
86542
87507
  if (nonModelProps.has(property.name)) {
87508
+ if (nonNullRefCallArgs && nullWidenedNonModelProps.has(property.name) && isNullWidenedPropObjectLiteralCallArgTarget(path, solidSetters)) {
87509
+ const localRead = _babel_types.memberExpression(_babel_types.identifier("local"), _babel_types.identifier(property.name));
87510
+ path.replaceWith(_babel_types.logicalExpression("??", localRead, _babel_types.identifier("undefined")));
87511
+ path.skip();
87512
+ return;
87513
+ }
86543
87514
  path.node.object = _babel_types.identifier("local");
86544
87515
  return;
86545
87516
  }
@@ -86553,7 +87524,8 @@ function rewriteRozieIdentifiers(cloned, ir) {
86553
87524
  }
86554
87525
  if (object.name === "$refs" && refNames.has(property.name)) {
86555
87526
  const refIdent = _babel_types.identifier(property.name + "Ref");
86556
- if (property.name === "__rozieRoot") path.replaceWith(_babel_types.tsNonNullExpression(refIdent));
87527
+ const isDirectCallArg = nonNullRefCallArgs && path.parentPath?.isCallExpression() && path.parentPath.node.arguments.includes(path.node);
87528
+ if (property.name === "__rozieRoot" || isDirectCallArg) path.replaceWith(_babel_types.tsNonNullExpression(refIdent));
86557
87529
  else path.replaceWith(refIdent);
86558
87530
  path.skip();
86559
87531
  return;
@@ -86934,6 +87906,19 @@ function genCode$1(node) {
86934
87906
  return generate$7(node, GEN_OPTS$6).code;
86935
87907
  }
86936
87908
  /**
87909
+ * Quick task 260714-orv — render hoisted user imports in ONE
87910
+ * @babel/generator pass so a comment shared between two adjacent imports
87911
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
87912
+ * later import's `leadingComments`) prints exactly once. Generating imports
87913
+ * one at a time (`nodes.map(genCode).join`) gives each import its OWN
87914
+ * comment-dedup set, doubling any shared comment. `t.program(nodes)` prints
87915
+ * only its body statements (no wrapping braces/`File` boilerplate), so
87916
+ * non-comment cases stay byte-identical.
87917
+ */
87918
+ function genImportsBlock(nodes) {
87919
+ return generate$7(_babel_types.program(nodes), GEN_OPTS$6).code;
87920
+ }
87921
+ /**
86937
87922
  * ROZ-cast-blindness fix — Solid emits each ComputedDecl's body via
86938
87923
  * `rewriteNode(c.body, ir)` directly from IR (never re-scanning the cloned
86939
87924
  * Program for the body itself), so there is no existing "find the cloned
@@ -86992,6 +87977,17 @@ function capitalize$6(name) {
86992
87977
  return name.charAt(0).toUpperCase() + name.slice(1);
86993
87978
  }
86994
87979
  /**
87980
+ * 260712-kl1 — exact-AST-shape gate mirroring the `isMutableLiteralFactoryDefault`
87981
+ * / `isNullWidenedPropObjectLiteralCallArgTarget` predicate convention. Matches
87982
+ * ONLY a Babel `NullLiteral` prop default (`default: null`) — never `undefined`,
87983
+ * `void 0`, or any other falsy/nullish shape. Used to widen a model prop's
87984
+ * `createControllableSignal<T>` generic to `T | null` (see 260712-kb9's
87985
+ * NEW-controllable-null-default catalog entry).
87986
+ */
87987
+ function isNullLiteralDefault(dv) {
87988
+ return _babel_types.isNullLiteral(dv);
87989
+ }
87990
+ /**
86995
87991
  * WR-01 fix (73-REVIEW.md, 73-10 gap-closure): collect every Identifier
86996
87992
  * binding name introduced by a declarator's `id` pattern — Identifier
86997
87993
  * directly, or recursively through ObjectPattern / ArrayPattern /
@@ -87204,7 +88200,7 @@ function emitScript$1(ir, collectors, _registry) {
87204
88200
  diagnostics.push(...rewriteResult.diagnostics);
87205
88201
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts } = partitionUserImports$1(rewriteResult.rewrittenProgram);
87206
88202
  rewriteResult.rewrittenProgram.program.body = bodyStmts;
87207
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$1(imp)).join("\n") + "\n" : "";
88203
+ const userImports = userImportNodes.length > 0 ? genImportsBlock(userImportNodes) + "\n" : "";
87208
88204
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => genCode$1(decl));
87209
88205
  const contextEmit = emitContext$1(ir, {
87210
88206
  solid: collectors.solidImports,
@@ -87220,7 +88216,9 @@ function emitScript$1(ir, collectors, _registry) {
87220
88216
  const dv = p.defaultValue;
87221
88217
  const raw = genCode$1(dv);
87222
88218
  const val = isMutableLiteralFactoryDefault(dv) ? `(${raw})()` : raw;
87223
- return `${p.name}: ${val}`;
88219
+ const ann = p.typeAnnotation;
88220
+ const entryVal = isMutableLiteralFactoryDefault(dv) && ann.kind === "identifier" && (ann.name === "Object" || ann.name === "Array") ? `${val} as ${renderType$1(ann)}` : val;
88221
+ return `${p.name}: ${entryVal}`;
87224
88222
  }).join(", ")} }, _props);\n`;
87225
88223
  }
87226
88224
  for (const p of ir.props) {
@@ -87234,7 +88232,8 @@ function emitScript$1(ir, collectors, _registry) {
87234
88232
  else dflt = raw;
87235
88233
  }
87236
88234
  const tsType = renderType$1(p.typeAnnotation);
87237
- hookLines.push(`const [${p.name}, ${setterName}] = createControllableSignal<${tsType}>(_props as unknown as Record<string, unknown>, '${p.name}', ${dflt});`);
88235
+ const genericType = isNullLiteralDefault(p.defaultValue) && tsType !== "unknown" ? `${tsType} | null` : tsType;
88236
+ hookLines.push(`const [${p.name}, ${setterName}] = createControllableSignal<${genericType}>(_props as unknown as Record<string, unknown>, '${p.name}', ${dflt});`);
87238
88237
  }
87239
88238
  for (const s of ir.state) {
87240
88239
  collectors.solidImports.add("createSignal");
@@ -87266,7 +88265,7 @@ function emitScript$1(ir, collectors, _registry) {
87266
88265
  for (const lh of ir.lifecycle) if (lh.phase === "mount") if (lh.cleanup) {
87267
88266
  collectors.solidImports.add("onMount");
87268
88267
  collectors.solidImports.add("onCleanup");
87269
- const rewrittenSetup = rewriteRozieExpressionNode(lh.setup, ir);
88268
+ const rewrittenSetup = rewriteRozieExpressionNode(lh.setup, ir, { nonNullRefCallArgs: true });
87270
88269
  const rewrittenCleanup = rewriteRozieExpressionNode(lh.cleanup, ir);
87271
88270
  if (_babel_types.isBlockStatement(lh.setup) && cleanupReferencesSetupLocal(lh.setup, lh.cleanup)) {
87272
88271
  const setupStatements = rewrittenSetup.body;
@@ -87280,7 +88279,7 @@ function emitScript$1(ir, collectors, _registry) {
87280
88279
  }
87281
88280
  } else {
87282
88281
  collectors.solidImports.add("onMount");
87283
- const arg = lifecycleArg(rewriteRozieExpressionNode(lh.setup, ir));
88282
+ const arg = lifecycleArg(rewriteRozieExpressionNode(lh.setup, ir, { nonNullRefCallArgs: true }));
87284
88283
  hookLines.push(`onMount(${arg});`);
87285
88284
  }
87286
88285
  else if (lh.phase === "unmount") {
@@ -89955,7 +90954,22 @@ function emitLoop$1(node, ctx) {
89955
90954
  invokeAccessors: ctx.invokeAccessors,
89956
90955
  loopValueBindings: new Set([...ctx.loopValueBindings ?? [], node.itemAlias])
89957
90956
  });
89958
- return `<Key each={${iterableCode} as readonly any[]} by={(${node.itemAlias}) => ${keyCode}}>{${aliasStr} => ${bodyJsx}}</Key>`;
90957
+ return `<Key each={${new Set([
90958
+ "Identifier",
90959
+ "MemberExpression",
90960
+ "OptionalMemberExpression",
90961
+ "CallExpression",
90962
+ "OptionalCallExpression",
90963
+ "NewExpression",
90964
+ "ArrayExpression",
90965
+ "ObjectExpression",
90966
+ "TSNonNullExpression",
90967
+ "TSAsExpression",
90968
+ "ParenthesizedExpression",
90969
+ "StringLiteral",
90970
+ "NumericLiteral",
90971
+ "TemplateLiteral"
90972
+ ]).has(node.iterableExpression.type) ? iterableCode : `(${iterableCode})`} as readonly any[]} by={(${node.itemAlias}) => ${keyCode}}>{${aliasStr} => ${bodyJsx}}</Key>`;
89959
90973
  }
89960
90974
  /**
89961
90975
  * Find an attribute by name.
@@ -90301,8 +91315,34 @@ function mergeEventAttributes(attrsJsx, eventsJsx, elementTag) {
90301
91315
  * (so the child is never hidden) while still changing value — and thus
90302
91316
  * still triggering `keyed` recreation — whenever the underlying key changes.
90303
91317
  */
91318
+ /**
91319
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
91320
+ * teleport. Wraps the element markup in Solid's native `<Show when={c}
91321
+ * fallback={tree}><Portal mount={c}>{tree}</Portal></Show>` — falsy
91322
+ * container renders `tree` in place via the `fallback` prop (never mounts
91323
+ * `<Portal>` at all), matching React's `container ? createPortal(tree,
91324
+ * container) : tree` and Vue's `<Teleport :disabled>` in-place semantics.
91325
+ *
91326
+ * SSR guard: `typeof document === 'undefined'` short-circuits the container
91327
+ * expression to `null` BEFORE it is evaluated at all — some author
91328
+ * expressions call `document.querySelector` internally (see
91329
+ * `resolveAppendTo`/`resolveTo`), so the guard must gate evaluation, not
91330
+ * just the result.
91331
+ */
91332
+ function emitPortalWrap(node, markup, ctx) {
91333
+ const { expression } = node.portalTo;
91334
+ if (ctx.elementPortalImport) ctx.elementPortalImport.needed = true;
91335
+ const containerCode = `(typeof document === 'undefined' ? null : (${rewriteTemplateExpression$1(expression, ctx.ir, {
91336
+ invokeAccessors: ctx.invokeAccessors,
91337
+ loopValueBindings: ctx.loopValueBindings,
91338
+ scopeAccessorParams: ctx.scopeAccessorParams
91339
+ })}))`;
91340
+ ctx.collectors.solid.add("Show");
91341
+ return `<Show when={${containerCode}} fallback={${markup}}><Portal mount={${containerCode}}>${markup}</Portal></Show>`;
91342
+ }
90304
91343
  function emitElement$1(origNode, ctx) {
90305
91344
  const markup = emitElementInner$1(origNode, ctx);
91345
+ if (origNode.portalTo) return emitPortalWrap(origNode, markup, ctx);
90306
91346
  if ((origNode.tagKind === "component" || origNode.tagKind === "self") && origNode.remountKeyExpression) {
90307
91347
  const keyExprCode = rewriteTemplateExpression$1(origNode.remountKeyExpression, ctx.ir, {
90308
91348
  invokeAccessors: ctx.invokeAccessors,
@@ -90540,11 +91580,13 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90540
91580
  jsx: "null",
90541
91581
  scriptInjections: [],
90542
91582
  needsKeyedImport: false,
90543
- diagnostics: []
91583
+ diagnostics: [],
91584
+ hasElementPortal: false
90544
91585
  };
90545
91586
  const scriptInjections = [];
90546
91587
  const injectionCounter = { next: 0 };
90547
91588
  const keyedImport = { needed: false };
91589
+ const elementPortalImport = { needed: false };
90548
91590
  const keynav = resolveKeynavPlan$1(ir);
90549
91591
  const ctx = {
90550
91592
  ir,
@@ -90554,6 +91596,7 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90554
91596
  scriptInjections,
90555
91597
  injectionCounter,
90556
91598
  keyedImport,
91599
+ elementPortalImport,
90557
91600
  keynav,
90558
91601
  ...opts.scopeAttr !== void 0 ? { scopeAttr: opts.scopeAttr } : {}
90559
91602
  };
@@ -90563,7 +91606,8 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90563
91606
  jsx,
90564
91607
  scriptInjections,
90565
91608
  needsKeyedImport: keyedImport.needed,
90566
- diagnostics
91609
+ diagnostics,
91610
+ hasElementPortal: elementPortalImport.needed
90567
91611
  };
90568
91612
  }
90569
91613
  //#endregion
@@ -91532,7 +92576,7 @@ function emitSolid(ir, opts = {}) {
91532
92576
  ...listenersResult.scriptInjections
91533
92577
  ].filter((s) => s.trim().length > 0).join("\n\n");
91534
92578
  const solidImportsStr = `import type { JSX } from 'solid-js';\n` + solidImports.render();
91535
- const portalImport = scriptResult.hasPortals ? "import { render } from 'solid-js/web';\n" : "";
92579
+ const portalImport = (scriptResult.hasPortals ? "import { render } from 'solid-js/web';\n" : "") + (templateResult.hasElementPortal ? "import { Portal } from 'solid-js/web';\n" : "");
91536
92580
  const keyedImport = templateResult.needsKeyedImport ? "import { Key } from '@solid-primitives/keyed';\n" : "";
91537
92581
  const shell = buildShell$1({
91538
92582
  componentName: ir.name,
@@ -93024,7 +94068,7 @@ function renderTsType(ann) {
93024
94068
  case "Boolean": return "boolean";
93025
94069
  case "Array": return "any[]";
93026
94070
  case "Object": return "any";
93027
- case "Function": return "((...args: unknown[]) => unknown) | null";
94071
+ case "Function": return "((...args: any[]) => any) | null";
93028
94072
  default: return ann.name;
93029
94073
  }
93030
94074
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -93053,14 +94097,81 @@ function renderTsType(ann) {
93053
94097
  * The `!`-shape matches what Rozie already emits for Angular `@Input` and Lit
93054
94098
  * `@query` ref fields.
93055
94099
  */
94100
+ /**
94101
+ * 260712-a09 (Pattern B) — an explicit `default: undefined` (or `default: void 0`)
94102
+ * is authorial shorthand for "no meaningful default", not a real default value.
94103
+ * `prop.defaultValue` is a live (non-null) AST node for either form, so the
94104
+ * `prop.defaultValue != null` guard below would otherwise route it into the
94105
+ * `= <default>` initializer branch and emit `foo: string = undefined` — a
94106
+ * TS2322 under strictNullChecks (a typed field can't be initialized with
94107
+ * `undefined` unless the type itself admits it). Detect both spellings here
94108
+ * and treat them identically to "no default" so they fall through to the
94109
+ * existing `?:`/`!:` branch.
94110
+ */
94111
+ function isUndefinedDefault(expr) {
94112
+ return _babel_types.isIdentifier(expr, { name: "undefined" }) || _babel_types.isUnaryExpression(expr) && expr.operator === "void";
94113
+ }
94114
+ /**
94115
+ * 260712-ig6 Task A — the full ARIAMixin field-name set (lib.dom.d.ts). A
94116
+ * prop authored with one of these names collides with LitElement's own
94117
+ * `implements ARIAMixin` field declaration (always `<T> | null`, NEVER
94118
+ * `undefined`-admitting) if it lands in the ordinary no-default `?:`/`!:`
94119
+ * branch below — Pattern B's `default: undefined` routes there and would
94120
+ * otherwise emit `ariaLabel?: string;` (`string | undefined`), which is not
94121
+ * assignable to the base class's `string | null` field (TS2416/TS1240).
94122
+ * Gated STRICTLY on name-set membership so every other prop name keeps its
94123
+ * pre-existing byte-identical `?:`/`!:` output.
94124
+ */
94125
+ const ARIA_MIXIN_FIELDS = new Set([
94126
+ "ariaAtomic",
94127
+ "ariaAutoComplete",
94128
+ "ariaBusy",
94129
+ "ariaChecked",
94130
+ "ariaColCount",
94131
+ "ariaColIndex",
94132
+ "ariaColSpan",
94133
+ "ariaCurrent",
94134
+ "ariaDescription",
94135
+ "ariaDisabled",
94136
+ "ariaExpanded",
94137
+ "ariaHasPopup",
94138
+ "ariaHidden",
94139
+ "ariaInvalid",
94140
+ "ariaKeyShortcuts",
94141
+ "ariaLabel",
94142
+ "ariaLevel",
94143
+ "ariaLive",
94144
+ "ariaModal",
94145
+ "ariaMultiLine",
94146
+ "ariaMultiSelectable",
94147
+ "ariaOrientation",
94148
+ "ariaPlaceholder",
94149
+ "ariaPosInSet",
94150
+ "ariaPressed",
94151
+ "ariaReadOnly",
94152
+ "ariaRequired",
94153
+ "ariaRoleDescription",
94154
+ "ariaRowCount",
94155
+ "ariaRowIndex",
94156
+ "ariaRowSpan",
94157
+ "ariaSelected",
94158
+ "ariaSetSize",
94159
+ "ariaSort",
94160
+ "ariaValueMax",
94161
+ "ariaValueMin",
94162
+ "ariaValueNow",
94163
+ "ariaValueText",
94164
+ "role"
94165
+ ]);
93056
94166
  function renderFieldSuffix(prop) {
93057
94167
  const tsType = renderTsType(prop.typeAnnotation);
93058
- if (prop.defaultValue != null) {
94168
+ if (prop.defaultValue != null && !isUndefinedDefault(prop.defaultValue)) {
93059
94169
  let fieldType = tsType;
93060
94170
  const admitsNull = /\bnull\b/.test(fieldType) || /(^|\|)\s*(any|unknown)\s*(\||$)/.test(fieldType);
93061
94171
  if (_babel_types.isNullLiteral(prop.defaultValue) && !admitsNull) fieldType = `${fieldType} | null`;
93062
94172
  return `: ${fieldType} = ${renderDefault(prop.defaultValue, prop.typeAnnotation)}`;
93063
94173
  }
94174
+ if (ARIA_MIXIN_FIELDS.has(prop.name)) return `: ${/\bnull\b/.test(tsType) || /(^|\|)\s*(any|unknown)\s*(\||$)/.test(tsType) ? tsType : `${tsType} | null`} = null`;
93064
94175
  return prop.required ? `!: ${tsType}` : `?: ${tsType}`;
93065
94176
  }
93066
94177
  function isPrimitiveType(ann) {
@@ -93433,12 +94544,15 @@ function emitScript(ir, opts) {
93433
94544
  const composedTypeImportLines = [];
93434
94545
  for (const typeName of composedTypeImports) {
93435
94546
  const decl = (ir.components ?? []).find((c) => c.localName === typeName);
93436
- if (decl) composedTypeImportLines.push(`import type { ${typeName} } from '${decl.importPath}';`);
94547
+ if (decl) {
94548
+ const importPath = isPublishedSpecifier(decl.importPath) ? rewriteRozieImport(decl.importPath, "lit") : decl.importPath;
94549
+ composedTypeImportLines.push(`import type { ${typeName} } from '${importPath}';`);
94550
+ }
93437
94551
  }
93438
94552
  const rewritten = rewriteScript(ir.setupBody.scriptProgram, ir, { runtime: opts.runtime });
93439
94553
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts: nonImportStmts } = partitionUserImports(rewritten.file);
93440
94554
  rewritten.file.program.body = nonImportStmts;
93441
- const userImports = (composedTypeImportLines.length > 0 ? composedTypeImportLines.join("\n") + "\n" : "") + (userImportNodes.length > 0 ? userImportNodes.map((imp) => generate$1(imp, GEN_OPTS$1).code).join("\n") + "\n" : "");
94555
+ const userImports = (composedTypeImportLines.length > 0 ? composedTypeImportLines.join("\n") + "\n" : "") + (userImportNodes.length > 0 ? generate$1(_babel_types.program(userImportNodes), GEN_OPTS$1).code + "\n" : "");
93442
94556
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => generate$1(decl, GEN_OPTS$1).code);
93443
94557
  const partition = partitionScript(rewritten.file);
93444
94558
  const computedNames = new Set(ir.computed.map((c) => c.name));
@@ -95166,6 +96280,7 @@ function emitElementOpenTag(node, ir, irName, opts) {
95166
96280
  const tagName = resolveTagName(node, irName);
95167
96281
  const parts = [];
95168
96282
  let refAttr = null;
96283
+ let portalSentinel = "";
95169
96284
  const rModelAttr = node.attributes.find((a) => attributeIsRModel(a));
95170
96285
  const staticClassValues = [];
95171
96286
  let bindingClass = null;
@@ -95268,16 +96383,27 @@ function emitElementOpenTag(node, ir, irName, opts) {
95268
96383
  parts.push(`\${rozieListeners(${expr})}`);
95269
96384
  }
95270
96385
  if (refAttr) parts.push(refAttr);
96386
+ if (node.portalTo && opts._state) {
96387
+ const fieldName = `__roziePortal${opts._state.portalCount++}`;
96388
+ parts.push(`data-rozie-portal-ref="${fieldName}"`);
96389
+ portalSentinel = `<span data-rozie-portal-anchor="${fieldName}" hidden></span>`;
96390
+ opts.decorators.add("query");
96391
+ opts.runtime.add("RoziePortalController");
96392
+ const containerCode = rewriteTemplateExpression(node.portalTo.expression, ir);
96393
+ opts._state.portalFieldDecls.push(` @query('[data-rozie-portal-ref="${fieldName}"]') private ${fieldName}!: HTMLElement;`);
96394
+ opts._state.portalFieldDecls.push(` @query('[data-rozie-portal-anchor="${fieldName}"]') private ${fieldName}Anchor!: HTMLElement;`);
96395
+ opts._state.portalFieldDecls.push(` private ${fieldName}Controller = new RoziePortalController(this, () => this.${fieldName}, () => this.${fieldName}Anchor, () => (${containerCode}));`);
96396
+ }
95271
96397
  const keynav = opts.keynav ?? null;
95272
96398
  parts.push(...keynavRootAttrs(keynav, node, opts.runtime), ...keynavItemAttrs(keynav, node, opts.keynavItemIndexAlias ?? null));
95273
96399
  if ((node.tagKind === "html" || node.tagKind === "component") && opts.scopeHash) parts.push(`data-rozie-s-${opts.scopeHash}`);
95274
96400
  const attrsText = parts.length > 0 ? " " + parts.join(" ") : "";
95275
96401
  if (isVoidElement(node.tagName) && node.children.length === 0) return {
95276
- open: `<${tagName}${attrsText} />`,
96402
+ open: `${portalSentinel}<${tagName}${attrsText} />`,
95277
96403
  selfClose: true
95278
96404
  };
95279
96405
  return {
95280
- open: `<${tagName}${attrsText}>`,
96406
+ open: `${portalSentinel}<${tagName}${attrsText}>`,
95281
96407
  selfClose: false
95282
96408
  };
95283
96409
  }
@@ -95546,6 +96672,8 @@ function emitTemplate(ir, opts) {
95546
96672
  slotFillerClassFields: [],
95547
96673
  slotFillerUpdatedBody: [],
95548
96674
  slotFillerDisconnectReset: [],
96675
+ portalFieldDecls: [],
96676
+ portalCount: 0,
95549
96677
  diagnostics
95550
96678
  };
95551
96679
  const keynav = resolveKeynavPlan(ir);
@@ -95574,6 +96702,7 @@ function emitTemplate(ir, opts) {
95574
96702
  slotFillerUpdatedBody: state.slotFillerUpdatedBody,
95575
96703
  slotFillerDisconnectReset: state.slotFillerDisconnectReset,
95576
96704
  keynavFieldDecls,
96705
+ portalFieldDecls: state.portalFieldDecls,
95577
96706
  diagnostics
95578
96707
  };
95579
96708
  }
@@ -95596,6 +96725,7 @@ function emitTemplate(ir, opts) {
95596
96725
  slotFillerUpdatedBody: state.slotFillerUpdatedBody,
95597
96726
  slotFillerDisconnectReset: state.slotFillerDisconnectReset,
95598
96727
  keynavFieldDecls,
96728
+ portalFieldDecls: state.portalFieldDecls,
95599
96729
  diagnostics
95600
96730
  };
95601
96731
  }
@@ -96056,7 +97186,11 @@ function emitStyle(styles, source, opts) {
96056
97186
  const engineCss = stringifyRules((styles.engineRules ?? []).flatMap((r) => r.children ?? []), source);
96057
97187
  const rawScopedCss = stringifyRules(scopedRules, source);
96058
97188
  const scopedCss = opts.scopeHash ? scopeCss(rawScopedCss, opts.scopeHash) : rawScopedCss;
96059
- const globalCss = [rootRules.length > 0 ? stringifyRules(rootRules, source) : "", engineCss].filter((s) => s.length > 0).join("\n");
97189
+ const globalCss = [
97190
+ rootRules.length > 0 ? stringifyRules(rootRules, source) : "",
97191
+ engineCss,
97192
+ opts.hasElementPortal === true ? scopedCss : ""
97193
+ ].filter((s) => s.length > 0).join("\n");
96060
97194
  const combinedScoped = [
96061
97195
  HOST_DISPLAY_DEFAULT,
96062
97196
  scopedCss,
@@ -96133,6 +97267,33 @@ function buildShell(parts) {
96133
97267
  }
96134
97268
  //#endregion
96135
97269
  //#region ../targets/lit/src/emitLit.ts
97270
+ /**
97271
+ * command-palette-portal-overlay phase — a cheap, INDEPENDENT presence-only
97272
+ * walk over `ir.template` for at least one `r-portal` element. Needed
97273
+ * because `emitStyle` runs BEFORE `emitTemplate` in this file's call
97274
+ * sequence (styles must be ready before the template walk references
97275
+ * `opts.scopeHash`), so the template-walk-derived `portalCount` isn't
97276
+ * available yet at the point `emitStyle` needs to know whether to ALSO
97277
+ * push `scopedCss` through `injectGlobalStyles` (see `emitStyle.ts`'s
97278
+ * `opts.hasElementPortal` doc comment / `RoziePortalController.ts`'s module
97279
+ * doc comment for WHY). A second, tiny boolean-only walk is simpler and
97280
+ * lower-risk than reordering the two emit passes or synchronizing two
97281
+ * independent per-element counters.
97282
+ */
97283
+ function hasElementPortal(node) {
97284
+ if (node === null) return false;
97285
+ switch (node.type) {
97286
+ case "TemplateElement":
97287
+ if (node.portalTo !== void 0) return true;
97288
+ return node.children.some(hasElementPortal);
97289
+ case "TemplateFragment": return node.children.some(hasElementPortal);
97290
+ case "TemplateConditional": return node.branches.some((b) => b.body.some(hasElementPortal));
97291
+ case "TemplateMatch": return node.branches.some((b) => b.body.some(hasElementPortal)) || node.hostElement !== void 0 && hasElementPortal(node.hostElement);
97292
+ case "TemplateLoop": return node.body.some(hasElementPortal);
97293
+ case "TemplateSlotInvocation": return node.fallback.some(hasElementPortal);
97294
+ default: return false;
97295
+ }
97296
+ }
96136
97297
  function emitLit(ir, opts = {}) {
96137
97298
  const litImports = new LitImportCollector();
96138
97299
  const decoratorImports = new LitDecoratorImportCollector();
@@ -96153,7 +97314,8 @@ function emitLit(ir, opts = {}) {
96153
97314
  componentName: ir.name,
96154
97315
  lit: litImports,
96155
97316
  runtime: runtimeImports,
96156
- scopeHash
97317
+ scopeHash,
97318
+ hasElementPortal: hasElementPortal(ir.template)
96157
97319
  });
96158
97320
  diagnostics.push(...styleResult.diagnostics);
96159
97321
  const scriptResult = emitScript(ir, {
@@ -96241,6 +97403,7 @@ function emitLit(ir, opts = {}) {
96241
97403
  debouncedFieldDecls: templateResult.debouncedFieldDecls.join("\n"),
96242
97404
  hoistedLiteralFieldDecls: templateResult.hoistedLiteralFieldDecls.join("\n"),
96243
97405
  keynavFieldDecls: templateResult.keynavFieldDecls.join("\n"),
97406
+ portalFieldDecls: templateResult.portalFieldDecls.join("\n"),
96244
97407
  slotFillerClassFields: templateResult.slotFillerClassFields.map((f) => " " + f).join("\n"),
96245
97408
  slotFields: slotResult.fields,
96246
97409
  cleanupField: " private _disconnectCleanups: Array<() => void> = [];\n // Re-parenting guard: set true once the deferred teardown has actually\n // run (a genuine un-mount), so a subsequent reconnect knows to re-arm.\n private _rozieTornDown = false;",
@@ -96317,6 +97480,7 @@ function composeClassBody(parts) {
96317
97480
  if (parts.debouncedFieldDecls.trim().length > 0) sections.push(parts.debouncedFieldDecls);
96318
97481
  if (parts.hoistedLiteralFieldDecls.trim().length > 0) sections.push(parts.hoistedLiteralFieldDecls);
96319
97482
  if (parts.keynavFieldDecls.trim().length > 0) sections.push(parts.keynavFieldDecls);
97483
+ if (parts.portalFieldDecls.trim().length > 0) sections.push(parts.portalFieldDecls);
96320
97484
  if (parts.slotFillerClassFields.trim().length > 0) sections.push(parts.slotFillerClassFields);
96321
97485
  if (parts.slotFields.trim().length > 0) sections.push(parts.slotFields);
96322
97486
  sections.push(parts.cleanupField);
@@ -96400,7 +97564,8 @@ function buildComponentImportsBlock(components, selfName) {
96400
97564
  const lines = [];
96401
97565
  for (const decl of components) {
96402
97566
  if (decl.localName === selfName) continue;
96403
- lines.push(`import '${decl.importPath}';`);
97567
+ const importPath = isPublishedSpecifier(decl.importPath) ? rewriteRozieImport(decl.importPath, "lit") : decl.importPath;
97568
+ lines.push(`import '${importPath}';`);
96404
97569
  }
96405
97570
  if (lines.length === 0) return void 0;
96406
97571
  return lines.join("\n") + "\n";
@@ -96515,9 +97680,9 @@ function compile(source, opts) {
96515
97680
  });
96516
97681
  const acc = [...parseDiags, ...irDiags];
96517
97682
  if (!ir) return guardEmpty(fail(acc));
96518
- threadParamTypes(ir, filename ?? "<anonymous>", cache, resolver, acc);
97683
+ threadParamTypes(ir, filename ?? "<anonymous>", cache, resolver, opts.target, acc);
96519
97684
  validatePortalScopedStyle(ir, acc);
96520
- validateTwoWayBindings(ir, filename ?? "<anonymous>", cache, resolver, acc);
97685
+ validateTwoWayBindings(ir, filename ?? "<anonymous>", cache, resolver, opts.target, acc);
96521
97686
  stampMissingFilename(acc, filename);
96522
97687
  if (acc.some((d) => d.severity === "error")) return fail(acc);
96523
97688
  const emitOpts = {