@rozie/babel-plugin 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +1279 -89
  2. package/dist/index.mjs +1268 -78
  3. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -1587,7 +1587,12 @@ const RozieErrorCode = {
1587
1587
  KEYNAV_ORPHAN_ITEM: "ROZ984",
1588
1588
  KEYNAV_BAD_FOCUS_MODEL: "ROZ985",
1589
1589
  KEYNAV_MULTIPLE_ROOTS: "ROZ986",
1590
- KEYNAV_SOURCE_UNRESOLVED: "ROZ987"
1590
+ KEYNAV_SOURCE_UNRESOLVED: "ROZ987",
1591
+ MANIFEST_SCHEMA_VERSION_MISMATCH: "ROZ988",
1592
+ MALFORMED_MANIFEST: "ROZ989",
1593
+ PORTAL_DIRECTIVE_ON_SLOT: "ROZ990",
1594
+ PORTAL_DIRECTIVE_ON_COMPONENT: "ROZ991",
1595
+ PORTAL_DIRECTIVE_EMPTY_VALUE: "ROZ992"
1591
1596
  };
1592
1597
  //#endregion
1593
1598
  //#region ../core/src/splitter/splitBlocks.ts
@@ -41192,6 +41197,13 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41192
41197
  const args = [];
41193
41198
  for (const attr of el.attributes) if (attr.kind === "static" && attr.name === "name" && attr.value !== null) slotName = attr.value;
41194
41199
  else if (attr.kind === "static" && attr.name === "portal" && attr.value === null) isPortal = true;
41200
+ else if (attr.kind === "directive" && attr.name === "portal") diagnostics.push({
41201
+ code: RozieErrorCode.PORTAL_DIRECTIVE_ON_SLOT,
41202
+ severity: "error",
41203
+ 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).",
41204
+ loc: attr.loc,
41205
+ 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."
41206
+ });
41195
41207
  else if (attr.kind === "binding" && attr.value !== null) {
41196
41208
  if (attr.name === "params") continue;
41197
41209
  const expr = tryParseExpression(attr.value);
@@ -41222,6 +41234,8 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41222
41234
  let keynavItem;
41223
41235
  let keynavActiveClassExpression;
41224
41236
  let keynavActiveClassDeps;
41237
+ let portalToExpression;
41238
+ let portalToDeps;
41225
41239
  for (const attr of el.attributes) {
41226
41240
  if (attr.kind === "event") {
41227
41241
  const handlerExpr = attr.value !== null ? tryParseExpression(attr.value) : null;
@@ -41431,6 +41445,34 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41431
41445
  isExternal = true;
41432
41446
  continue;
41433
41447
  }
41448
+ if (attr.name === "portal") {
41449
+ if (attr.value === null || attr.value.trim() === "") {
41450
+ diagnostics.push({
41451
+ code: RozieErrorCode.PORTAL_DIRECTIVE_EMPTY_VALUE,
41452
+ severity: "error",
41453
+ message: "r-portal requires a container expression (e.g. r-portal=\"$props.appendTo\") — an empty value is not a valid portal target.",
41454
+ loc: attr.loc,
41455
+ hint: "Provide an expression that evaluates to an Element (or a falsy value / null to render in place)."
41456
+ });
41457
+ continue;
41458
+ }
41459
+ if (elementIsComponent) {
41460
+ diagnostics.push({
41461
+ code: RozieErrorCode.PORTAL_DIRECTIVE_ON_COMPONENT,
41462
+ severity: "error",
41463
+ message: "r-portal is not supported on a <components>-registered child component element (v1 limitation) — only plain/host elements can be portalled.",
41464
+ loc: attr.loc,
41465
+ hint: "Wrap the component in a plain host element and put r-portal on that wrapper instead."
41466
+ });
41467
+ continue;
41468
+ }
41469
+ const expr = tryParseExpression(attr.value);
41470
+ if (expr) {
41471
+ portalToExpression = expr;
41472
+ portalToDeps = computeExpressionDeps(expr, bindings);
41473
+ }
41474
+ continue;
41475
+ }
41434
41476
  if ((attr.name === "model" || attr.name === "show" || attr.name === "html" || attr.name === "text") && attr.value !== null) {
41435
41477
  const expr = tryParseExpression(attr.value);
41436
41478
  const modifiers = attr.name === "model" ? resolveModelModifiers(attr.chain, registry, diagnostics) : [];
@@ -41482,7 +41524,11 @@ function lowerBareElement(el, bindings, depGraph, registry, diagnostics, templat
41482
41524
  ...isExternal ? { isExternal: true } : {},
41483
41525
  ...remountKeyExpression !== void 0 ? { remountKeyExpression } : {},
41484
41526
  ...keynavRoot !== void 0 ? { keynavRoot } : {},
41485
- ...keynavItem !== void 0 ? { keynavItem } : {}
41527
+ ...keynavItem !== void 0 ? { keynavItem } : {},
41528
+ ...portalToExpression !== void 0 ? { portalTo: {
41529
+ expression: portalToExpression,
41530
+ deps: portalToDeps ?? []
41531
+ } } : {}
41486
41532
  };
41487
41533
  }
41488
41534
  /**
@@ -55808,6 +55854,340 @@ var IRCache = class {
55808
55854
  }
55809
55855
  };
55810
55856
  //#endregion
55857
+ //#region ../core/src/manifest/buildManifest.ts
55858
+ var import_lib = /* @__PURE__ */ __toESM(require_lib$6(), 1);
55859
+ typeof import_lib.default === "function" ? import_lib.default : import_lib.default.default;
55860
+ //#endregion
55861
+ //#region ../core/src/manifest/readManifest.ts
55862
+ /**
55863
+ * parseManifest(raw) — validate + deserialize a published-primitive manifest
55864
+ * into a producer slot/prop/expose surface (Phase 75, D-04).
55865
+ *
55866
+ * Fails CLOSED on any trust-boundary violation (T-75-01): an unknown or
55867
+ * mismatched `schemaVersion` — or any structurally malformed field — never
55868
+ * silently degrades into a partial/best-effort surface. Both failure modes
55869
+ * return `{ surface: null, error }`; parseManifest NEVER throws (T-75-02: a
55870
+ * crafted/oversized paramType source string is dropped per-entry, not fatal).
55871
+ *
55872
+ * Per T-75-03, every field is read via explicit key access into a FRESH
55873
+ * object — parseManifest never `Object.assign`/spreads the parsed JSON into
55874
+ * a shared target, so a `__proto__`/`constructor`-keyed manifest cannot
55875
+ * pollute anything beyond the literal-keyed surface it produces.
55876
+ *
55877
+ * @experimental — shape may change before v1.0
55878
+ */
55879
+ function malformed(message) {
55880
+ return {
55881
+ surface: null,
55882
+ error: {
55883
+ code: RozieErrorCode.MALFORMED_MANIFEST,
55884
+ message
55885
+ }
55886
+ };
55887
+ }
55888
+ function schemaMismatch(message) {
55889
+ return {
55890
+ surface: null,
55891
+ error: {
55892
+ code: RozieErrorCode.MANIFEST_SCHEMA_VERSION_MISMATCH,
55893
+ message
55894
+ }
55895
+ };
55896
+ }
55897
+ function isPlainObject(v) {
55898
+ return typeof v === "object" && v !== null && !Array.isArray(v);
55899
+ }
55900
+ /**
55901
+ * Deserialize one paramType SOURCE STRING back into a `TSType` by parsing
55902
+ * `type __RozieParam = <src>;` and lifting the type-alias's typeAnnotation.
55903
+ * Wrapped in try/catch — a garbage/oversized string is dropped (returns
55904
+ * `null`) rather than throwing out of parseManifest (T-75-02).
55905
+ */
55906
+ function deserializeParamType(src) {
55907
+ try {
55908
+ const stmt = (0, import_lib$3.parse)(`type __RozieParam = ${src};`, {
55909
+ sourceType: "module",
55910
+ plugins: ["typescript"]
55911
+ }).program.body[0];
55912
+ if (!stmt || !t$3.isTSTypeAliasDeclaration(stmt)) return null;
55913
+ return stmt.typeAnnotation;
55914
+ } catch {
55915
+ return null;
55916
+ }
55917
+ }
55918
+ /** Validate + rebuild one manifest prop entry. Returns null on structural violation. */
55919
+ function readProp(raw) {
55920
+ if (!isPlainObject(raw)) return null;
55921
+ const name = raw.name;
55922
+ const isModel = raw.isModel;
55923
+ if (typeof name !== "string") return null;
55924
+ if (typeof isModel !== "boolean") return null;
55925
+ return {
55926
+ name,
55927
+ isModel
55928
+ };
55929
+ }
55930
+ /** Validate + rebuild one manifest slot-param entry. Returns null on structural violation. */
55931
+ function readSlotParam(raw) {
55932
+ if (!isPlainObject(raw)) return null;
55933
+ const name = raw.name;
55934
+ if (typeof name !== "string") return null;
55935
+ return {
55936
+ type: "ParamDecl",
55937
+ name,
55938
+ valueExpression: t$3.identifier(name),
55939
+ sourceLoc: {
55940
+ start: 0,
55941
+ end: 0
55942
+ }
55943
+ };
55944
+ }
55945
+ /** Validate + rebuild one manifest slot entry into a minimal SlotDecl. */
55946
+ function readSlot(raw) {
55947
+ if (!isPlainObject(raw)) return null;
55948
+ const name = raw.name;
55949
+ const paramsRaw = raw.params;
55950
+ const paramTypesRaw = raw.paramTypes;
55951
+ const isPortal = raw.isPortal;
55952
+ const isReactive = raw.isReactive;
55953
+ if (typeof name !== "string") return null;
55954
+ if (!Array.isArray(paramsRaw)) return null;
55955
+ if (paramTypesRaw !== null && !Array.isArray(paramTypesRaw)) return null;
55956
+ if (typeof isPortal !== "boolean") return null;
55957
+ if (typeof isReactive !== "boolean") return null;
55958
+ const params = [];
55959
+ for (const p of paramsRaw) {
55960
+ const param = readSlotParam(p);
55961
+ if (param === null) return null;
55962
+ params.push(param);
55963
+ }
55964
+ let paramTypes;
55965
+ if (paramTypesRaw !== null) {
55966
+ const deserialized = [];
55967
+ for (const src of paramTypesRaw) {
55968
+ if (typeof src !== "string") continue;
55969
+ const ty = deserializeParamType(src);
55970
+ if (ty !== null) deserialized.push(ty);
55971
+ }
55972
+ paramTypes = deserialized;
55973
+ }
55974
+ return {
55975
+ type: "SlotDecl",
55976
+ name,
55977
+ defaultContent: null,
55978
+ params,
55979
+ ...paramTypes !== void 0 ? { paramTypes } : {},
55980
+ presence: "always",
55981
+ nestedSlots: [],
55982
+ sourceLoc: {
55983
+ start: 0,
55984
+ end: 0
55985
+ },
55986
+ isPortal,
55987
+ isReactive
55988
+ };
55989
+ }
55990
+ /** Validate + rebuild one manifest expose entry. Returns null on structural violation. */
55991
+ function readExposeMember(raw) {
55992
+ if (!isPlainObject(raw)) return null;
55993
+ const name = raw.name;
55994
+ if (typeof name !== "string") return null;
55995
+ return { name };
55996
+ }
55997
+ /**
55998
+ * Validate a manifest's `schemaVersion` FIRST (fail closed, D-04), then
55999
+ * shape-validate props/slots/emits/expose and deserialize into a
56000
+ * `ProducerSurface` consumable by `threadParamTypes` / `validateTwoWayBindings`.
56001
+ * Never throws (T-75-01/T-75-02).
56002
+ */
56003
+ function parseManifest(raw, _opts) {
56004
+ if (!isPlainObject(raw)) return malformed("Manifest is not a JSON object.");
56005
+ const schemaVersion = raw.schemaVersion;
56006
+ if (typeof schemaVersion !== "number") return malformed("Manifest is missing a numeric \"schemaVersion\" field.");
56007
+ 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.`);
56008
+ const propsRaw = raw.props;
56009
+ const slotsRaw = raw.slots;
56010
+ const emitsRaw = raw.emits;
56011
+ const exposeRaw = raw.expose;
56012
+ if (!Array.isArray(propsRaw)) return malformed("Manifest \"props\" is not an array.");
56013
+ if (!Array.isArray(slotsRaw)) return malformed("Manifest \"slots\" is not an array.");
56014
+ if (!Array.isArray(emitsRaw)) return malformed("Manifest \"emits\" is not an array.");
56015
+ if (!Array.isArray(exposeRaw)) return malformed("Manifest \"expose\" is not an array.");
56016
+ const props = [];
56017
+ for (const p of propsRaw) {
56018
+ const prop = readProp(p);
56019
+ if (prop === null) return malformed("Manifest \"props\" entry is malformed.");
56020
+ props.push(prop);
56021
+ }
56022
+ const slots = [];
56023
+ for (const s of slotsRaw) {
56024
+ const slot = readSlot(s);
56025
+ if (slot === null) return malformed("Manifest \"slots\" entry is malformed.");
56026
+ slots.push(slot);
56027
+ }
56028
+ for (const e of emitsRaw) if (typeof e !== "string") return malformed("Manifest \"emits\" entry is not a string.");
56029
+ const expose = [];
56030
+ for (const m of exposeRaw) {
56031
+ const member = readExposeMember(m);
56032
+ if (member === null) return malformed("Manifest \"expose\" entry is malformed.");
56033
+ expose.push(member);
56034
+ }
56035
+ return {
56036
+ surface: {
56037
+ slots,
56038
+ props,
56039
+ expose
56040
+ },
56041
+ error: null
56042
+ };
56043
+ }
56044
+ //#endregion
56045
+ //#region ../core/src/manifest/resolveManifestProducer.ts
56046
+ /**
56047
+ * resolveManifestProducer — Phase 75 Plan 02 (D-08/D-09/D-10).
56048
+ *
56049
+ * Wires the manifest contract (Plan 01's `buildManifest`/`parseManifest`)
56050
+ * into `@rozie/core`'s producer-resolution seam so a PUBLISHED cross-package
56051
+ * `<components>` specifier resolves a producer contract from a compiled
56052
+ * `rozie-manifest.json` instead of a `.rozie` source file.
56053
+ *
56054
+ * **COMP-04 bounded exception**: local `.rozie` composition (relative /
56055
+ * tsconfig-alias specifiers) still needs NO cross-`.rozie` IR type info and
56056
+ * keeps reading on-disk `.rozie` source unchanged via the existing
56057
+ * `resolver.resolveProducerPath` + `IRCache.getIRComponent` path. ONLY
56058
+ * published cross-PACKAGE specifiers (D-08's `@scope/pkg/Component.rozie`
56059
+ * shape) read this compiled manifest artifact. This module and its two
56060
+ * call-sites (threadParamTypes.ts, validateTwoWayBindings.ts) are the entire
56061
+ * surface of that exception.
56062
+ *
56063
+ * Convention-based per-target package derivation (D-08): for a published
56064
+ * specifier `@scope/pkg/Component.rozie`, the trailing `/Component.rozie`
56065
+ * segment is stripped to yield the base package `@scope/pkg`, then
56066
+ * `-${target}` is appended to yield the derived per-target package name
56067
+ * (e.g. `@rozie-ui/combobox` + `-react` → `@rozie-ui/combobox-react`).
56068
+ *
56069
+ * Location (D-10): the derived package is located via the SAME
56070
+ * `enhanced-resolve` layer `ProducerResolver` already uses for `.rozie`
56071
+ * producer resolution — walking `node_modules` from the consumer file's
56072
+ * directory (NOT from `ResolverOptions.root`, which feeds only the
56073
+ * tsconfig-paths matcher). Because a real per-target leaf's `package.json`
56074
+ * `exports` map does not (and need not) list `"./package.json"` or
56075
+ * `"./rozie-manifest.json"` as exported subpaths, this resolver deliberately
56076
+ * resolves the package's bare `"."` export entry (its normal `import`
56077
+ * condition target, e.g. `dist/index.mjs`) and then walks UP from that
56078
+ * resolved file to the nearest ancestor directory whose `package.json`
56079
+ * `name` matches the derived package name (T-75-04/T-75-05 — bounded walk,
56080
+ * name-verified, never an unauthenticated `../` traversal). The manifest is
56081
+ * then read at a FIXED literal relative path inside that directory
56082
+ * (`rozie-manifest.json`) — no specifier-derived path segment ever reaches
56083
+ * `readFileSync` (T-75-04).
56084
+ *
56085
+ * @experimental — shape may change before v1.0
56086
+ */
56087
+ /** Tsconfig-alias-shaped prefix (empty npm scope, e.g. `@/components/Modal.rozie`). */
56088
+ const TSCONFIG_ALIAS_PREFIX$1 = "@/";
56089
+ /**
56090
+ * True iff `specifier` is a PUBLISHED cross-package `<components>` specifier
56091
+ * (D-08 shape): ends in `.rozie`, is neither a relative/absolute path nor a
56092
+ * tsconfig `paths`-alias-shaped specifier.
56093
+ *
56094
+ * `isPublishedSpecifier` is a pure, syntactic classifier — it does not itself
56095
+ * consult a real tsconfig (that happens inside `ProducerResolver`'s local
56096
+ * COMP-04 path when this returns `false`). A leading `@/` is NOT a valid npm
56097
+ * scoped-package prefix (npm scopes require at least one character between
56098
+ * `@` and the following `/`), so it is the reliable syntactic signal that a
56099
+ * `@/...` specifier is tsconfig-alias-shaped rather than a real scoped
56100
+ * package — matching `ProducerResolver`'s own documented `@/components/Modal.rozie`
56101
+ * example of a tsconfig-paths alias.
56102
+ */
56103
+ function isPublishedSpecifier$1(specifier) {
56104
+ if (!specifier.endsWith(".rozie")) return false;
56105
+ if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
56106
+ if (specifier.startsWith(TSCONFIG_ALIAS_PREFIX$1)) return false;
56107
+ return true;
56108
+ }
56109
+ /**
56110
+ * Strip the trailing `/<Component>.rozie` leaf off a published specifier to
56111
+ * derive the base package name (e.g. `@rozie-ui/combobox/Combobox.rozie` →
56112
+ * `@rozie-ui/combobox`). Returns `null` when the specifier has no `/`
56113
+ * separating a package from a leaf (malformed — never a valid published
56114
+ * specifier in practice, since `isPublishedSpecifier` requires `.rozie`).
56115
+ */
56116
+ function deriveBasePackageName(specifier) {
56117
+ const lastSlash = specifier.lastIndexOf("/");
56118
+ if (lastSlash <= 0) return null;
56119
+ return specifier.slice(0, lastSlash);
56120
+ }
56121
+ /**
56122
+ * Walk UP from a resolved entry-file path to the nearest ancestor directory
56123
+ * whose `package.json` `name` field equals `expectedName` AND which carries a
56124
+ * sibling `rozie-manifest.json`. Bounded to 10 levels (T-75-05 — a real
56125
+ * per-target leaf's package root sits 1-3 levels above its resolved `dist/*`
56126
+ * entry; 10 is a generous, still-finite bound that can never escape into an
56127
+ * unrelated ancestor tree undetected, since EVERY level is name-verified
56128
+ * before being accepted).
56129
+ *
56130
+ * Requiring the manifest sibling (not just a name match) is deliberate: some
56131
+ * per-target build tooling generates a SECONDARY `package.json` inside the
56132
+ * `dist/` output with the SAME `name` field as the real package root (e.g.
56133
+ * `ng-packagr`'s Angular `Package Format v9+` legacy-entry-point convention
56134
+ * emits `dist/package.json`). A name-only match would stop at that inner
56135
+ * duplicate — one level too shallow — and never reach the real package root
56136
+ * where `rozie-manifest.json` actually lives (only shipped at the root,
56137
+ * sibling to the AUTHORED `package.json`, never duplicated into `dist/`).
56138
+ * Continuing the walk past a name-matched-but-manifest-less directory finds
56139
+ * the correct root for every current `@rozie-ui/*-<target>` leaf shape.
56140
+ */
56141
+ function findPackageRoot(entryFilePath, expectedName) {
56142
+ let dir = dirname(entryFilePath);
56143
+ for (let i = 0; i < 10; i++) {
56144
+ try {
56145
+ const raw = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
56146
+ if (typeof raw === "object" && raw !== null && raw.name === expectedName) try {
56147
+ readFileSync(join(dir, "rozie-manifest.json"), "utf-8");
56148
+ return dir;
56149
+ } catch {}
56150
+ } catch {}
56151
+ const parent = dirname(dir);
56152
+ if (parent === dir) return null;
56153
+ dir = parent;
56154
+ }
56155
+ return null;
56156
+ }
56157
+ function lookupFailed(pkgLabel) {
56158
+ return {
56159
+ surface: null,
56160
+ error: {
56161
+ code: RozieErrorCode.CROSS_PACKAGE_LOOKUP_FAILED,
56162
+ 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.`
56163
+ }
56164
+ };
56165
+ }
56166
+ /**
56167
+ * Derive the per-target package for a published `<components>` specifier,
56168
+ * locate its installed `rozie-manifest.json` via node module resolution, and
56169
+ * validate + deserialize it into a `ProducerSurface` (D-08/D-09/D-10/D-04).
56170
+ *
56171
+ * Never throws — every failure mode returns `{ surface: null, error }`.
56172
+ */
56173
+ function resolveManifestProducer(args) {
56174
+ const { specifier, target, fromFile, resolver } = args;
56175
+ const basePkg = deriveBasePackageName(specifier);
56176
+ if (basePkg === null) return lookupFailed(specifier);
56177
+ const derivedPkg = `${basePkg}-${target}`;
56178
+ const entryFilePath = resolver.resolveProducerPath(derivedPkg, fromFile);
56179
+ if (entryFilePath === null) return lookupFailed(derivedPkg);
56180
+ const packageDir = findPackageRoot(entryFilePath, derivedPkg);
56181
+ if (packageDir === null) return lookupFailed(derivedPkg);
56182
+ let raw;
56183
+ try {
56184
+ raw = JSON.parse(readFileSync(join(packageDir, "rozie-manifest.json"), "utf-8"));
56185
+ } catch {
56186
+ return lookupFailed(derivedPkg);
56187
+ }
56188
+ return parseManifest(raw, { packageName: derivedPkg });
56189
+ }
56190
+ //#endregion
55811
56191
  //#region ../core/src/ir/threadParamTypes.ts
55812
56192
  /**
55813
56193
  * Recursive template walker: visit every TemplateNode in source order.
@@ -55892,9 +56272,11 @@ function bodyContainsReprojection(body) {
55892
56272
  * the cache reverse-dep edge + resolver `fromFile` argument)
55893
56273
  * @param cache - per-compiler-instance IRCache (D-01)
55894
56274
  * @param resolver - per-compiler-instance ProducerResolver (D-02 / D-12)
56275
+ * @param target - the per-call compile target (Phase 75 Plan 02 —
56276
+ * selects the derived per-target package for a PUBLISHED specifier; D-09)
55895
56277
  * @param diagnostics - accumulator
55896
56278
  */
55897
- function threadParamTypes(ir, consumerPath, cache, resolver, diagnostics) {
56279
+ function threadParamTypes(ir, consumerPath, cache, resolver, target, diagnostics) {
55898
56280
  const declaredSlotNames = new Set(ir.slots.map((s) => s.name));
55899
56281
  const { reprojections } = collectSlotInvocationsByContext(ir.template);
55900
56282
  for (const reproj of reprojections) if (!declaredSlotNames.has(reproj.slotName)) diagnostics.push({
@@ -55914,6 +56296,26 @@ function threadParamTypes(ir, consumerPath, cache, resolver, diagnostics) {
55914
56296
  if (node.tagKind === "self") {
55915
56297
  producerSlots = ir.slots;
55916
56298
  producerProps = ir.props;
56299
+ } else if (isPublishedSpecifier$1(node.componentRef.importPath)) {
56300
+ const { surface, error } = resolveManifestProducer({
56301
+ specifier: node.componentRef.importPath,
56302
+ target,
56303
+ fromFile: consumerPath,
56304
+ resolver
56305
+ });
56306
+ if (error !== null) {
56307
+ diagnostics.push({
56308
+ code: error.code,
56309
+ severity: "error",
56310
+ message: error.message,
56311
+ loc: node.componentRef.sourceLoc,
56312
+ hint: "Verify the per-target package is installed as a dependency and ships a compatible rozie-manifest.json."
56313
+ });
56314
+ return;
56315
+ }
56316
+ if (surface === null) return;
56317
+ producerSlots = surface.slots;
56318
+ producerProps = surface.props;
55917
56319
  } else {
55918
56320
  const resolvedPath = resolver.resolveProducerPath(node.componentRef.importPath, consumerPath);
55919
56321
  if (resolvedPath === null) {
@@ -56118,9 +56520,11 @@ function walkTemplate(node, visit) {
56118
56520
  * @param cache - per-compiler-instance IRCache (D-01) — shared with
56119
56521
  * threadParamTypes so producers parsed once during threading are reused here
56120
56522
  * @param resolver - per-compiler-instance ProducerResolver (D-02 / D-12)
56523
+ * @param target - the per-call compile target (Phase 75 Plan 02 —
56524
+ * selects the derived per-target package for a PUBLISHED specifier; D-09)
56121
56525
  * @param diagnostics - accumulator (mutated in place; ROZ949/950/951/945 pushed)
56122
56526
  */
56123
- function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics) {
56527
+ function validateTwoWayBindings(ir, consumerPath, cache, resolver, target, diagnostics) {
56124
56528
  walkTemplate(ir.template, (node) => {
56125
56529
  if (node.type !== "TemplateElement") return;
56126
56530
  const twoWayAttrs = node.attributes.filter((a) => a.kind === "twoWayBinding");
@@ -56135,6 +56539,27 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56135
56539
  return producerProps;
56136
56540
  }
56137
56541
  if (node.tagKind !== "component" || !node.componentRef) return null;
56542
+ if (isPublishedSpecifier$1(node.componentRef.importPath)) {
56543
+ const { surface, error } = resolveManifestProducer({
56544
+ specifier: node.componentRef.importPath,
56545
+ target,
56546
+ fromFile: consumerPath,
56547
+ resolver
56548
+ });
56549
+ if (error !== null) {
56550
+ diagnostics.push({
56551
+ code: error.code,
56552
+ severity: "error",
56553
+ message: error.message,
56554
+ loc: node.componentRef.sourceLoc,
56555
+ hint: "Verify the per-target package is installed as a dependency and ships a compatible rozie-manifest.json."
56556
+ });
56557
+ return null;
56558
+ }
56559
+ if (surface === null) return null;
56560
+ producerProps = surface.props;
56561
+ return producerProps;
56562
+ }
56138
56563
  const resolvedPath = resolver.resolveProducerPath(node.componentRef.importPath, consumerPath);
56139
56564
  if (resolvedPath === null) {
56140
56565
  diagnostics.push({
@@ -56189,6 +56614,7 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56189
56614
  if (producerProp === void 0) continue;
56190
56615
  if (!producerProp.isModel) {
56191
56616
  const producerImportPath = node.tagKind === "self" ? "(self-reference)" : node.componentRef?.importPath ?? "(unknown)";
56617
+ const relatedLoc = producerProp.sourceLoc ?? node.componentRef?.sourceLoc ?? attr.sourceLoc;
56192
56618
  diagnostics.push({
56193
56619
  code: RozieErrorCode.TWO_WAY_PROP_NOT_MODEL,
56194
56620
  severity: "error",
@@ -56197,7 +56623,7 @@ function validateTwoWayBindings(ir, consumerPath, cache, resolver, diagnostics)
56197
56623
  hint: `Add 'model: true' to the '${attr.name}' prop declaration on ${producerImportPath}, or pass a one-way value via :${attr.name}="…" instead.`,
56198
56624
  related: [{
56199
56625
  message: `'${attr.name}' declared here without model: true`,
56200
- loc: producerProp.sourceLoc
56626
+ loc: relatedLoc
56201
56627
  }]
56202
56628
  });
56203
56629
  continue;
@@ -56508,10 +56934,37 @@ const TARGET_EXT_MAP = {
56508
56934
  lit: ""
56509
56935
  };
56510
56936
  const ROZIE_EXT$1 = ".rozie";
56937
+ /** Tsconfig-alias-shaped prefix (empty npm scope, e.g. `@/components/Modal.rozie`). */
56938
+ const TSCONFIG_ALIAS_PREFIX = "@/";
56939
+ /**
56940
+ * True iff `importPath` is a PUBLISHED cross-package `<components>`
56941
+ * specifier (D-08 shape): ends in `.rozie`, is neither a relative/absolute
56942
+ * path nor a tsconfig `paths`-alias-shaped specifier. Mirrors
56943
+ * `resolveManifestProducer.ts`'s `isPublishedSpecifier` (kept duplicated,
56944
+ * not imported, so this module stays dependency-free — both are tiny, pure,
56945
+ * syntactic classifiers over the same D-08 specifier shape and must be kept
56946
+ * in lockstep if that shape ever changes).
56947
+ *
56948
+ * Exported so callers that do NOT already route an import path through
56949
+ * `rewriteRozieImport` (e.g. the Lit emitter's side-effect/type-only
56950
+ * composed-component imports, which deliberately leave a LOCAL `.rozie`
56951
+ * specifier verbatim for the unplugin-consumer-coexist flow) can still
56952
+ * gate a published-specifier rewrite without duplicating this check.
56953
+ */
56954
+ function isPublishedSpecifier(importPath) {
56955
+ if (!importPath.endsWith(ROZIE_EXT$1)) return false;
56956
+ if (importPath.startsWith(".") || importPath.startsWith("/")) return false;
56957
+ if (importPath.startsWith(TSCONFIG_ALIAS_PREFIX)) return false;
56958
+ return true;
56959
+ }
56511
56960
  function rewriteRozieImport(importPath, target) {
56512
56961
  if (!importPath.endsWith(ROZIE_EXT$1)) return importPath;
56513
56962
  const targetExt = TARGET_EXT_MAP[target];
56514
56963
  if (targetExt === void 0) throw new Error(`rewriteRozieImport: unknown target '${target}' — expected one of vue|react|svelte|angular|solid|lit`);
56964
+ if (isPublishedSpecifier(importPath)) {
56965
+ const lastSlash = importPath.lastIndexOf("/");
56966
+ if (lastSlash > 0) return `${importPath.slice(0, lastSlash)}-${target}`;
56967
+ }
56515
56968
  return importPath.slice(0, -6) + targetExt;
56516
56969
  }
56517
56970
  //#endregion
@@ -56903,7 +57356,6 @@ function deconflictVueRefSuffix(ir, topLevelBindingNames) {
56903
57356
  }
56904
57357
  //#endregion
56905
57358
  //#region ../core/src/codegen/buildPropJsdoc.ts
56906
- var import_lib = /* @__PURE__ */ __toESM(require_lib$6(), 1);
56907
57359
  /**
56908
57360
  * Neutralize the JSDoc comment terminator inside an author-controlled string so
56909
57361
  * it cannot prematurely close the emitted comment block (T-58-04). Replaces each
@@ -57795,6 +58247,22 @@ function renderComputedArrow$3(body, cast) {
57795
58247
  * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
57796
58248
  * mirrored trailing copy spaces correctly.
57797
58249
  */
58250
+ /**
58251
+ * Quick task 260714-orv — compare two Babel comment nodes by SOURCE POSITION
58252
+ * (type + starting line/column) rather than object reference. A comment
58253
+ * shared between two adjacent statements' trailing/leading arrays sometimes
58254
+ * IS the exact same object, but an earlier pipeline pass (hoistModuleLet /
58255
+ * rewriteRozieIdentifiers / etc.) may clone an individual statement, minting
58256
+ * a content-identical comment object at a different reference. Two distinct,
58257
+ * independently-authored comments can never share a starting source
58258
+ * position, so this is a safe substitute for `===` this late in the
58259
+ * pipeline.
58260
+ */
58261
+ function isSameSourceComment$1(a, b) {
58262
+ if (a === b) return true;
58263
+ if (!a.loc || !b.loc) return false;
58264
+ return a.type === b.type && a.loc.start.line === b.loc.start.line && a.loc.start.column === b.loc.start.column;
58265
+ }
57798
58266
  function mirrorSpliceBoundaryComments$2(stmts) {
57799
58267
  for (let i = 1; i < stmts.length; i++) {
57800
58268
  const cur = stmts[i];
@@ -57803,7 +58271,15 @@ function mirrorSpliceBoundaryComments$2(stmts) {
57803
58271
  const prevExtra = prev.extra;
57804
58272
  const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
57805
58273
  const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
57806
- if (!curSpliced && !prevSpliced) continue;
58274
+ if (!curSpliced && !prevSpliced) {
58275
+ const prevTrail = prev.trailingComments;
58276
+ const curLead = cur.leadingComments;
58277
+ if (prevTrail && prevTrail.length > 0 && curLead && curLead.length > 0) {
58278
+ const deduped = prevTrail.filter((c) => !curLead.some((lc) => isSameSourceComment$1(c, lc)));
58279
+ prev.trailingComments = deduped.length > 0 ? deduped : null;
58280
+ }
58281
+ continue;
58282
+ }
57807
58283
  const lead = cur.leadingComments;
57808
58284
  const prevTrail = prev.trailingComments;
57809
58285
  if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
@@ -59914,7 +60390,61 @@ function emitElement$5(node, ctx) {
59914
60390
  * (`<template #header="{ close }">…</template>`); default-shorthand
59915
60391
  * (`{ name: '' }`) emits as bare children inside the component tag.
59916
60392
  */
60393
+ /**
60394
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
60395
+ * teleport. Wraps the element in Vue's NATIVE `<Teleport :to :disabled>`
60396
+ * component — authors cannot write `<Teleport>` directly (ROZ926 rejects
60397
+ * it), but the EMITTER may synthesize it as the lowering target for
60398
+ * `r-portal` (ROZ926 gates author input, not emit output). `<Teleport>`
60399
+ * tolerates a `v-if`/`v-for` child, so `extraDirective` (the r-if/r-for
60400
+ * directive the parent conditional/loop walker wants to inject) is applied
60401
+ * to the INNER element, not the `<Teleport>` wrapper — the wrapper is
60402
+ * unconditional; when `disabled` the child's own `v-if` still governs
60403
+ * whether anything renders at all.
60404
+ *
60405
+ * `:disabled="!(<expr>)"` — a falsy container renders in place (Vue's
60406
+ * Teleport skips target resolution/warnings entirely when disabled; see
60407
+ * `resolveTarget`/`mountToTarget` in @vue/runtime-core — no console warning,
60408
+ * no throw, for a falsy `to` when `disabled` is true).
60409
+ *
60410
+ * Finding 4 (R2) — the container expression is HOISTED into a single Vue
60411
+ * `computed` (the same D-04 hoist pattern `r-match` uses) instead of being
60412
+ * spliced verbatim into both `:to` and `:disabled`. This fixes three
60413
+ * defects at once:
60414
+ * 1. SSR guard — the computed body wraps the expression in the SAME
60415
+ * `typeof document === 'undefined' ? null : (...)` guard the React and
60416
+ * Solid targets emit, so a container expression that touches `document`
60417
+ * (e.g. `document.querySelector(...)`) is null on the server rather
60418
+ * than throwing; a null container makes `:disabled` truthy → renders
60419
+ * in place (parity with React's ternary / Solid's <Show> fallback).
60420
+ * 2. single evaluation — a Vue computed caches, so a side-effecting
60421
+ * selector runs once per render, not twice (`:to` + `:disabled`).
60422
+ * 3. escaping — the raw expression now lives in the script (where
60423
+ * @babel/generator escapes JS strings), and `:to`/`:disabled` bind a
60424
+ * bare computed identifier, so an expression containing a `"` can no
60425
+ * longer break the double-quoted attribute (obviating a per-attribute
60426
+ * escapeAttrValue call at the interpolation site).
60427
+ */
60428
+ function emitPortalElement$1(node, extraDirective, ctx) {
60429
+ const { expression } = node.portalTo;
60430
+ if (ctx.elementPortalFlag) ctx.elementPortalFlag.seen = true;
60431
+ const containerCode = rewriteTemplateExpression$5(expression, ctx.ir);
60432
+ const hoistName = `__roziePortalTo${ctx.injectionCounter.next++}`;
60433
+ ctx.scriptInjections.push({
60434
+ wrapName: hoistName,
60435
+ import: {
60436
+ from: "vue",
60437
+ name: "computed"
60438
+ },
60439
+ decl: `const ${hoistName} = computed(() => (typeof document === 'undefined' ? null : (${containerCode})));`
60440
+ });
60441
+ return `<Teleport :to="${hoistName}" :disabled="!${hoistName}">${emitElementWithExtraDirectiveInner(node, extraDirective, ctx)}</Teleport>`;
60442
+ }
59917
60443
  function emitElementWithExtraDirective(origNode, extraDirective, ctx) {
60444
+ if (origNode.portalTo) return emitPortalElement$1(origNode, extraDirective, ctx);
60445
+ return emitElementWithExtraDirectiveInner(origNode, extraDirective, ctx);
60446
+ }
60447
+ function emitElementWithExtraDirectiveInner(origNode, extraDirective, ctx) {
59918
60448
  const node = stripKeynavCommitEvent$5(origNode);
59919
60449
  const rHtml = findRHtml$2(node.attributes);
59920
60450
  const attrText = emitMergedAttributes(rHtml ? node.attributes.filter((a) => !(a.kind === "binding" && a.name === "r-html")) : node.attributes, {
@@ -65011,6 +65541,20 @@ function genBlockInner(block, indent) {
65011
65541
  }).join("\n");
65012
65542
  }
65013
65543
  /**
65544
+ * Quick task 260714-orv — render hoisted user imports in ONE
65545
+ * @babel/generator pass so a comment shared between two adjacent imports
65546
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
65547
+ * later import's `leadingComments`) prints exactly once. Mirrors
65548
+ * `genBlockInner`'s single-pass precedent — generating imports one at a time
65549
+ * (`nodes.map(genCode).join`) gives each import its OWN comment-dedup set,
65550
+ * doubling any shared comment. `t.program(nodes)` prints only its body
65551
+ * statements (no wrapping braces/`File` boilerplate), so non-comment cases
65552
+ * stay byte-identical.
65553
+ */
65554
+ function genImportsBlock$2(nodes) {
65555
+ return generate$21(t$3.program(nodes), GEN_OPTS$19).code;
65556
+ }
65557
+ /**
65014
65558
  * Phase 14.1 follow-up — does any element in the IR template carry a
65015
65559
  * `spreadBinding` attribute? The Plan 14-05 gate at `emitScript` only
65016
65560
  * synthesises `const attrs = ...` (the source for the `$attrs` rewrite at
@@ -65862,7 +66406,7 @@ function emitScript$4(ir, collectors, opts = {}) {
65862
66406
  const cloned = cloneScriptProgram$4(ir.setupBody.scriptProgram);
65863
66407
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts } = partitionUserImports$3(cloned);
65864
66408
  cloned.program.body = bodyStmts;
65865
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$7(imp)).join("\n") + "\n" : "";
66409
+ const userImports = userImportNodes.length > 0 ? genImportsBlock$2(userImportNodes) + "\n" : "";
65866
66410
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => genCode$7(decl));
65867
66411
  deconflictDeclareThenAssignRef(cloned, ir);
65868
66412
  const hoistResult = hoistModuleLet(cloned, ir);
@@ -69246,7 +69790,42 @@ function scopeAttrForElement$1(node, ctx) {
69246
69790
  * declarations are hoisted within their containing scope). Both emit the tag
69247
69791
  * verbatim PascalCase below; no template AST rewrite needed.
69248
69792
  */
69793
+ /**
69794
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
69795
+ * teleport. Wraps the normal element emit (`emitElementInner`) in a
69796
+ * `container ? createPortal(tree, container) : tree` check, using React's
69797
+ * native `createPortal` (react-dom) — NEVER the P33 `emitPortals.ts`
69798
+ * createRoot-into-container machinery (that is the INVERSE direction: slot-
69799
+ * content mounted INTO an engine-owned container, not a component's own
69800
+ * subtree relocated OUT).
69801
+ *
69802
+ * SSR guard: the container expression is evaluated ONLY when `document`
69803
+ * exists — some author expressions call `document.querySelector` internally
69804
+ * (see `resolveAppendTo`/`resolveTo` in the authoring convention), so simply
69805
+ * checking the RESULT for null is not enough; the whole resolution must be
69806
+ * skipped on the server. Falsy/null container falls back to rendering the
69807
+ * subtree in place — this is what makes `appendTo:false` byte-behavior-
69808
+ * identical to no directive at emit time (the lower-time field is always
69809
+ * present per Task 1; this is the runtime decision point).
69810
+ *
69811
+ * Returns a `{...}` JSX-expression-container mustache — valid directly in
69812
+ * JSX children position, and `renderBranchBody` (emitConditional.ts) strips
69813
+ * the outer braces via `stripBalancedMustache` when this is a single-child
69814
+ * r-if branch body, so `r-portal` + `r-if` on the same element compose for
69815
+ * free with zero special-casing in the conditional emitter.
69816
+ */
69817
+ function emitPortalElement(node, ctx) {
69818
+ const { expression } = node.portalTo;
69819
+ if (ctx.elementPortalFlag) ctx.elementPortalFlag.seen = true;
69820
+ const containerCode = rewriteTemplateExpression$4(expression, ctx.ir);
69821
+ const treeJsx = emitElementInner$4(node, ctx);
69822
+ return `{(() => { const __rozieContainer = typeof document === 'undefined' ? null : (${containerCode}); return __rozieContainer ? createPortal(${treeJsx}, __rozieContainer) : (${treeJsx}); })()}`;
69823
+ }
69249
69824
  function emitElement$4(origNode, ctx) {
69825
+ if (origNode.portalTo) return emitPortalElement(origNode, ctx);
69826
+ return emitElementInner$4(origNode, ctx);
69827
+ }
69828
+ function emitElementInner$4(origNode, ctx) {
69250
69829
  let node = stripKeynavCommitEvent$4(origNode);
69251
69830
  const pendingKey = ctx.pendingKey ?? null;
69252
69831
  const childCtx = {
@@ -69796,9 +70375,11 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69796
70375
  scriptInjections: [],
69797
70376
  slotPropFields: slotResult.slotPropFields,
69798
70377
  slotCtxInterfaces: slotResult.slotCtxInterfaces,
69799
- diagnostics: []
70378
+ diagnostics: [],
70379
+ hasElementPortal: false
69800
70380
  };
69801
70381
  const keynav = resolveKeynavPlan$4(ir);
70382
+ const elementPortalFlag = { seen: false };
69802
70383
  const ctx = {
69803
70384
  ir,
69804
70385
  collectors,
@@ -69807,7 +70388,8 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69807
70388
  scriptInjections,
69808
70389
  injectionCounter: { next: 0 },
69809
70390
  ...opts.scopeAttr !== void 0 ? { scopeAttr: opts.scopeAttr } : {},
69810
- keynav
70391
+ keynav,
70392
+ elementPortalFlag
69811
70393
  };
69812
70394
  const jsx = emitNode$4(ir.template, ctx);
69813
70395
  if (keynav !== null) scriptInjections.push(...buildKeynavScriptInjections$2(keynav, ir, collectors));
@@ -69816,7 +70398,8 @@ function emitTemplate$4(ir, collectors, registry, opts = {}) {
69816
70398
  scriptInjections,
69817
70399
  slotPropFields: slotResult.slotPropFields,
69818
70400
  slotCtxInterfaces: slotResult.slotCtxInterfaces,
69819
- diagnostics
70401
+ diagnostics,
70402
+ hasElementPortal: elementPortalFlag.seen
69820
70403
  };
69821
70404
  }
69822
70405
  //#endregion
@@ -73736,11 +74319,11 @@ function emitReact(ir, opts = {}) {
73736
74319
  runtime: runtimeImports,
73737
74320
  filename: opts.filename
73738
74321
  }, scriptOpts);
73739
- const portalImport = hasPortals ? "import { createRoot, type Root } from 'react-dom/client';\nimport { flushSync } from 'react-dom';\n" : "";
73740
74322
  const tmpl = emitTemplate$4(ir, {
73741
74323
  react: reactImports,
73742
74324
  runtime: runtimeImports
73743
74325
  }, registry, { scopeAttr });
74326
+ 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" : "");
73744
74327
  const listeners = emitListeners$4(ir, {
73745
74328
  react: reactImports,
73746
74329
  runtime: runtimeImports
@@ -73941,11 +74524,11 @@ function renderPropType(ann) {
73941
74524
  case "Boolean": return "boolean";
73942
74525
  case "Array": return "unknown[]";
73943
74526
  case "Object": return "Record<string, unknown>";
73944
- case "Function": return "(...args: unknown[]) => unknown";
74527
+ case "Function": return "(...args: any[]) => any";
73945
74528
  default: return ann.name;
73946
74529
  }
73947
74530
  if (ann.kind === "literal") switch (ann.value) {
73948
- case "function": return "(...args: unknown[]) => unknown";
74531
+ case "function": return "(...args: any[]) => any";
73949
74532
  case "object": return "Record<string, unknown>";
73950
74533
  case "array": return "unknown[]";
73951
74534
  case "string": return "string";
@@ -75231,6 +75814,22 @@ function arrowBody$2(body) {
75231
75814
  * `normalizeSplicedEmitLines` (core) has already anchored the seam spacing, so the
75232
75815
  * mirrored trailing copy spaces correctly.
75233
75816
  */
75817
+ /**
75818
+ * Quick task 260714-orv — compare two Babel comment nodes by SOURCE POSITION
75819
+ * (type + starting line/column) rather than object reference. A comment
75820
+ * shared between two adjacent statements' trailing/leading arrays sometimes
75821
+ * IS the exact same object, but an earlier pipeline pass (hoistModuleLet /
75822
+ * rewriteRozieIdentifiers / etc.) may clone an individual statement, minting
75823
+ * a content-identical comment object at a different reference. Two distinct,
75824
+ * independently-authored comments can never share a starting source
75825
+ * position, so this is a safe substitute for `===` this late in the
75826
+ * pipeline.
75827
+ */
75828
+ function isSameSourceComment(a, b) {
75829
+ if (a === b) return true;
75830
+ if (!a.loc || !b.loc) return false;
75831
+ return a.type === b.type && a.loc.start.line === b.loc.start.line && a.loc.start.column === b.loc.start.column;
75832
+ }
75234
75833
  function mirrorSpliceBoundaryComments$1(stmts) {
75235
75834
  for (let i = 1; i < stmts.length; i++) {
75236
75835
  const cur = stmts[i];
@@ -75239,7 +75838,15 @@ function mirrorSpliceBoundaryComments$1(stmts) {
75239
75838
  const prevExtra = prev.extra;
75240
75839
  const curSpliced = curExtra?.__roziePartialOrigin !== void 0;
75241
75840
  const prevSpliced = prevExtra?.__roziePartialOrigin !== void 0;
75242
- if (!curSpliced && !prevSpliced) continue;
75841
+ if (!curSpliced && !prevSpliced) {
75842
+ const prevTrail = prev.trailingComments;
75843
+ const curLead = cur.leadingComments;
75844
+ if (prevTrail && prevTrail.length > 0 && curLead && curLead.length > 0) {
75845
+ const deduped = prevTrail.filter((c) => !curLead.some((lc) => isSameSourceComment(c, lc)));
75846
+ prev.trailingComments = deduped.length > 0 ? deduped : null;
75847
+ }
75848
+ continue;
75849
+ }
75243
75850
  const lead = cur.leadingComments;
75244
75851
  const prevTrail = prev.trailingComments;
75245
75852
  if (curSpliced && !prevSpliced && (!lead || lead.length === 0) && prevTrail && prevTrail.length > 0) {
@@ -77775,6 +78382,11 @@ function emitElementInner$3(origNode, ctx) {
77775
78382
  hasFallthroughSpread = true;
77776
78383
  break;
77777
78384
  }
78385
+ let portalAttr = null;
78386
+ if (node.portalTo) {
78387
+ ctx.runtimeImports.add("roziePortal");
78388
+ portalAttr = `use:roziePortal={${rewriteTemplateExpression$3(node.portalTo.expression, ctx.ir, ctxRenames(ctx))}}`;
78389
+ }
77778
78390
  const attrText = emitAttributes$2(node.attributes, {
77779
78391
  ir: ctx.ir,
77780
78392
  elementTagKind: node.tagKind,
@@ -77803,6 +78415,7 @@ function emitElementInner$3(origNode, ctx) {
77803
78415
  if (eventText) partsHead.push(eventText);
77804
78416
  for (const sp of spreadTexts) partsHead.push(sp);
77805
78417
  for (const ka of keynavAttrs) partsHead.push(ka);
78418
+ if (portalAttr) partsHead.push(portalAttr);
77806
78419
  if (ctx.scopeAttr.length > 0) partsHead.push(ctx.scopeAttr);
77807
78420
  const head = partsHead.length > 0 ? " " + partsHead.join(" ") : "";
77808
78421
  if (rHtml !== null) {
@@ -79534,6 +80147,88 @@ function hoistDoubleReadAccessors(program) {
79534
80147
  }
79535
80148
  visit(program.program);
79536
80149
  }
80150
+ /** True for `$props.X` where X is in `polymorphicModelProps`. */
80151
+ function isPolymorphicModelRead$1(node, polymorphicModelProps) {
80152
+ return t$3.isMemberExpression(node) && !node.computed && t$3.isIdentifier(node.object) && node.object.name === "$props" && t$3.isIdentifier(node.property) && polymorphicModelProps.has(node.property.name);
80153
+ }
80154
+ /**
80155
+ * When `test` is `typeof $props.X === '<lit>'` (or `!==`) or `'<lit>' in
80156
+ * $props.X`, for X in `polymorphicModelProps`, returns X. Otherwise null.
80157
+ */
80158
+ function matchesPolymorphicModelGuard$1(test, polymorphicModelProps) {
80159
+ if (t$3.isLogicalExpression(test)) return matchesPolymorphicModelGuard$1(test.left, polymorphicModelProps) ?? matchesPolymorphicModelGuard$1(test.right, polymorphicModelProps);
80160
+ if (!t$3.isBinaryExpression(test)) return null;
80161
+ const { operator, left, right } = test;
80162
+ if (operator === "===" || operator === "!==") {
80163
+ if (t$3.isUnaryExpression(left) && left.operator === "typeof" && isPolymorphicModelRead$1(left.argument, polymorphicModelProps) && t$3.isStringLiteral(right)) return left.argument.property.name;
80164
+ if (t$3.isUnaryExpression(right) && right.operator === "typeof" && isPolymorphicModelRead$1(right.argument, polymorphicModelProps) && t$3.isStringLiteral(left)) return right.argument.property.name;
80165
+ return null;
80166
+ }
80167
+ if (operator === "in" && isPolymorphicModelRead$1(right, polymorphicModelProps)) return right.property.name;
80168
+ return null;
80169
+ }
80170
+ /**
80171
+ * Quick task 260711-v2l (project_angular_typeof_narrow_gap) — port of Solid's
80172
+ * `hoistPolymorphicModelGuards` (emitter-hardening backlog item #11,
80173
+ * `project_solid_polymorphic_model_typeof_narrow_gap`, 73-02 Task 2) to the
80174
+ * Angular target. `$props.X` (a union or `unknown`-typed `model: true` prop)
80175
+ * lowers to an Angular signal accessor CALL (`this.X()`); TS does not narrow a
80176
+ * `typeof`/`in` guard across two SEPARATE calls to that accessor the way it
80177
+ * narrows a plain variable/property read. When a `ConditionalExpression`
80178
+ * guards on `typeof $props.X === '<lit>'` / `'<lit>' in $props.X` (X a
80179
+ * polymorphic/unknown model prop) AND re-reads `$props.X` again inside the
80180
+ * conditional, hoist ONE local binding before the guard and route every
80181
+ * `$props.X` read inside the conditional through it — mirroring the hand-
80182
+ * authored `const v = $props.value; return typeof v === 'string' ? v : ''`
80183
+ * workaround this replaces (DatePicker.rozie, commit bf3766b5).
80184
+ *
80185
+ * Gated to two known-safe shapes so this never mis-hoists into the wrong scope:
80186
+ * 1. the conditional IS an arrow function's own concise body — converted to
80187
+ * a block body wrapping `const v = ...; return <conditional>;`
80188
+ * 2. the conditional sits inside a statement that lives in a statement LIST
80189
+ * (block body, switch-case consequent, program body) — including
80190
+ * statements nested inside control-flow blocks (`if`/`for`/`while`/
80191
+ * `switch`/`try`). The local is inserted right before that statement, in
80192
+ * the same block as the guarded reads (Spike-012 BUG-4).
80193
+ * A conditional not in either shape (e.g. a bare single-statement `if (x)
80194
+ * return …;` with no block) is left alone (falsify-to-no-op, never a wrong fix).
80195
+ *
80196
+ * Runs on the raw (pre-$props-rewrite) Program, before the main
80197
+ * `rewriteRozieIdentifiers` traversal — the injected `$props.X` reference is
80198
+ * left for that later pass to lower to the normal `this.X()` accessor call
80199
+ * (identical downstream lowering to the DatePicker hand-authored workaround
80200
+ * this replaces).
80201
+ */
80202
+ function hoistPolymorphicModelGuards$1(cloned, polymorphicModelProps) {
80203
+ if (polymorphicModelProps.size === 0) return;
80204
+ traverse$8(cloned, { ConditionalExpression(path) {
80205
+ const propName = matchesPolymorphicModelGuard$1(path.node.test, polymorphicModelProps);
80206
+ if (!propName) return;
80207
+ let occurrences = 0;
80208
+ path.traverse({ MemberExpression(inner) {
80209
+ if (isPolymorphicModelRead$1(inner.node, polymorphicModelProps) && inner.node.property.name === propName) occurrences++;
80210
+ } });
80211
+ if (occurrences < 2) return;
80212
+ const parentPath = path.parentPath;
80213
+ const isConciseArrowBody = parentPath.isArrowFunctionExpression() && parentPath.node.body === path.node;
80214
+ const stmtPath = path.getStatementParent();
80215
+ const canInsertBeforeStmt = !!stmtPath && stmtPath.inList;
80216
+ if (!isConciseArrowBody && !canInsertBeforeStmt) return;
80217
+ const localName = path.scope.hasBinding("v") ? path.scope.generateUidIdentifier("v").name : "v";
80218
+ path.traverse({ MemberExpression(inner) {
80219
+ if (isPolymorphicModelRead$1(inner.node, polymorphicModelProps) && inner.node.property.name === propName) {
80220
+ inner.replaceWith(t$3.identifier(localName));
80221
+ inner.skip();
80222
+ }
80223
+ } });
80224
+ const varDecl = t$3.variableDeclaration("const", [t$3.variableDeclarator(t$3.identifier(localName), t$3.memberExpression(t$3.identifier("$props"), t$3.identifier(propName)))]);
80225
+ if (isConciseArrowBody) {
80226
+ parentPath.node.body = t$3.blockStatement([varDecl, t$3.returnStatement(path.node)]);
80227
+ return;
80228
+ }
80229
+ stmtPath.insertBefore(varDecl);
80230
+ } });
80231
+ }
79537
80232
  /**
79538
80233
  * Rewrite Rozie magic-accessor identifiers in-place on a cloned Program.
79539
80234
  *
@@ -81751,6 +82446,19 @@ function genCode$3(node) {
81751
82446
  return generate$9(node, GEN_OPTS$8).code;
81752
82447
  }
81753
82448
  /**
82449
+ * Quick task 260714-orv — render hoisted user imports in ONE
82450
+ * @babel/generator pass so a comment shared between two adjacent imports
82451
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
82452
+ * later import's `leadingComments`) prints exactly once. Generating imports
82453
+ * one at a time (`nodes.map(genCode).join`) gives each import its OWN
82454
+ * comment-dedup set, doubling any shared comment. `t.program(nodes)` prints
82455
+ * only its body statements (no wrapping braces/`File` boilerplate), so
82456
+ * non-comment cases stay byte-identical.
82457
+ */
82458
+ function genImportsBlock$1(nodes) {
82459
+ return generate$9(t$3.program(nodes), GEN_OPTS$8).code;
82460
+ }
82461
+ /**
81754
82462
  * Emit `() => body` for an Expression or BlockStatement body.
81755
82463
  *
81756
82464
  * Building the arrow as a Babel node (rather than string-templating
@@ -81833,7 +82541,7 @@ function renderType$2(ann) {
81833
82541
  case "Boolean": return "boolean";
81834
82542
  case "Array": return "any[]";
81835
82543
  case "Object": return "Record<string, any>";
81836
- case "Function": return "(...args: unknown[]) => unknown";
82544
+ case "Function": return "(...args: any[]) => any";
81837
82545
  default: return ann.name;
81838
82546
  }
81839
82547
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -81843,7 +82551,7 @@ function renderType$2(ann) {
81843
82551
  if (ann.kind === "literal") {
81844
82552
  if (ann.value === "array") return "any[]";
81845
82553
  if (ann.value === "object") return "Record<string, any>";
81846
- if (ann.value === "function") return "(...args: unknown[]) => unknown";
82554
+ if (ann.value === "function") return "(...args: any[]) => any";
81847
82555
  return ann.value;
81848
82556
  }
81849
82557
  return "unknown";
@@ -82158,8 +82866,9 @@ function emitScript$2(ir, opts = {}) {
82158
82866
  deconflictReservedImportBindings(cloned, new Set([...ir.props.map((p) => p.name), ...reservedClassMembers("angular", { singleModel: importSingleModel })]));
82159
82867
  const { userImports: userImportNodes, hoistedTypeDecls, bodyStmts, valueImportNames } = partitionUserImports$2(cloned);
82160
82868
  cloned.program.body = bodyStmts;
82161
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$3(imp)).join("\n") + "\n" : "";
82869
+ const userImports = userImportNodes.length > 0 ? genImportsBlock$1(userImportNodes) + "\n" : "";
82162
82870
  normalizeModelAccessor$2(cloned);
82871
+ hoistPolymorphicModelGuards$1(cloned, new Set(ir.props.filter((p) => p.isModel && (p.typeAnnotation.kind === "union" || renderType$2(p.typeAnnotation) === "unknown")).map((p) => p.name)));
82163
82872
  hoistDoubleReadAccessors(cloned);
82164
82873
  const lifecyclePairing = pairClonedLifecycle(cloned, ir);
82165
82874
  const emitsWithPayload = collectEmitsWithPayload(cloned);
@@ -82797,6 +83506,42 @@ function shouldWrapAttrBinding$1(name, expr, ctx, elementTagName) {
82797
83506
  return true;
82798
83507
  }
82799
83508
  /**
83509
+ * Quick task 260711-tgk — Angular's `[style]=` (`ɵɵstyleMap`) parses the bound
83510
+ * value into a semicolon-delimited key:value map. Its parser corrupts on an
83511
+ * EMPTY declaration (`;;` — e.g. `pinStyle(colId) + ';' + pad` when
83512
+ * `pinStyle` already ends in `;`), silently DROPPING the declaration that
83513
+ * follows the empty segment. The other 5 targets apply a `:style` STRING
83514
+ * value via the browser's own (tolerant) CSS parser — Angular is the lone
83515
+ * outlier because `[style]=` binds through `ɵɵstyleMap`, not `setAttribute`.
83516
+ *
83517
+ * Fix (see the call site in emitSingleAttr's binding branch): when a
83518
+ * `:style` binding's expression is PROVABLY a string at compile time, emit
83519
+ * `[attr.style]="<expr>"` instead of `[style]="<expr>"`. `[attr.style]`
83520
+ * compiles to `ɵɵattribute` → `setAttribute('style', <string>)`, which the
83521
+ * browser parses tolerantly — matching the other 5 targets' behavior. This
83522
+ * is only reachable for the element's SOLE style contributor: emitAttributes'
83523
+ * `counts.get('style') > 1` merge path (`[ngStyle]`) always intercepts
83524
+ * multi-source style bindings (a co-existing static `style=` or a second
83525
+ * `:style`) via its own `continue`, before emitSingleAttr is ever called for
83526
+ * a style attribute in that case.
83527
+ *
83528
+ * Conservative by design: a false NEGATIVE (returning false for a value that
83529
+ * IS always a string) just keeps the current, already-correct-for-non-`;;`
83530
+ * `[style]=` behavior. A false POSITIVE (returning true for a value that can
83531
+ * be an object) would break object-valued styles — so every branch below is
83532
+ * additive-only and defaults to `false`.
83533
+ */
83534
+ function isProvablyStringStyleExpression(expr) {
83535
+ if (t$3.isStringLiteral(expr)) return true;
83536
+ if (t$3.isTemplateLiteral(expr)) return true;
83537
+ if (t$3.isBinaryExpression(expr) && expr.operator === "+") {
83538
+ if (!t$3.isExpression(expr.left)) return false;
83539
+ return isProvablyStringStyleExpression(expr.left) || isProvablyStringStyleExpression(expr.right);
83540
+ }
83541
+ if (t$3.isConditionalExpression(expr)) return isProvablyStringStyleExpression(expr.consequent) && isProvablyStringStyleExpression(expr.alternate);
83542
+ return false;
83543
+ }
83544
+ /**
82800
83545
  * Plan 14-05 R6 — keys that must never reach the emitted object from an
82801
83546
  * author-controlled `r-bind` LITERAL. Mirrors the React/Solid/Vue/Svelte
82802
83547
  * `FORBIDDEN_SPREAD_KEYS` set + the Phase 02 `collectPropDecls` write-time
@@ -83206,6 +83951,151 @@ function emitListenerSpread$1(spread, ctx) {
83206
83951
  return `#${refName}`;
83207
83952
  }
83208
83953
  /**
83954
+ * command-palette-portal-overlay phase — the SHARED (once-per-component)
83955
+ * `__roziePortalPlace` helper method. Mirrors `applyAttrsHelperDecl` /
83956
+ * `listenersRendererFieldDecl`'s dedup pattern: pushed onto
83957
+ * `ctx.scriptInjections` at most once regardless of how many `r-portal`
83958
+ * elements the component has.
83959
+ *
83960
+ * A `WeakMap<Element, anchor>` lazily captures each portalled element's
83961
+ * ORIGINAL parent + next-sibling on first placement (so a later falsy
83962
+ * `target` restores it to its exact natural template position — the AOT-
83963
+ * safe, signals-era analog of Vue's `<Teleport :disabled>` / React's
83964
+ * ternary in-place fallback). A truthy `target` moves the element via
83965
+ * native `appendChild` and records it in `__roziePortalMoved` so the
83966
+ * shared `ngOnDestroy` teardown (registered by the per-portal effect, see
83967
+ * `emitPortalDirective`) can remove any STILL-relocated node — Angular's
83968
+ * own view-destroy machinery is not guaranteed to find a node that has
83969
+ * been moved outside its logical view position.
83970
+ */
83971
+ const ROZIE_PORTAL_PLACE_FIELD_NAME = "__roziePortalPlace";
83972
+ const ROZIE_PORTAL_ANCHORS_FIELD_NAME = "__roziePortalAnchors";
83973
+ const ROZIE_PORTAL_MOVED_FIELD_NAME = "__roziePortalMoved";
83974
+ function portalAnchorsFieldDecl() {
83975
+ return `private ${ROZIE_PORTAL_ANCHORS_FIELD_NAME} = new WeakMap<Element, { parent: Node | null; next: Node | null }>();`;
83976
+ }
83977
+ function portalMovedFieldDecl() {
83978
+ return `private ${ROZIE_PORTAL_MOVED_FIELD_NAME} = new Set<Element>();`;
83979
+ }
83980
+ function portalPlaceMethodDecl() {
83981
+ return [
83982
+ `private ${ROZIE_PORTAL_PLACE_FIELD_NAME}(el: Element, target: Element | null | undefined): void {`,
83983
+ ` let anchor = this.${ROZIE_PORTAL_ANCHORS_FIELD_NAME}.get(el);`,
83984
+ ` if (!anchor) {`,
83985
+ ` anchor = { parent: el.parentNode, next: el.nextSibling };`,
83986
+ ` this.${ROZIE_PORTAL_ANCHORS_FIELD_NAME}.set(el, anchor);`,
83987
+ ` }`,
83988
+ ` if (target) {`,
83989
+ ` if (!this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.has(el) && !el.isConnected) return;`,
83990
+ ` if (el.parentNode !== target) {`,
83991
+ ` target.appendChild(el);`,
83992
+ ` }`,
83993
+ ` this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.add(el);`,
83994
+ ` return;`,
83995
+ ` }`,
83996
+ ` if (!this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.has(el)) return;`,
83997
+ ` this.${ROZIE_PORTAL_MOVED_FIELD_NAME}.delete(el);`,
83998
+ ` if (anchor.parent) {`,
83999
+ ` if (anchor.next && anchor.next.parentNode === anchor.parent) {`,
84000
+ ` anchor.parent.insertBefore(el, anchor.next);`,
84001
+ ` } else {`,
84002
+ ` anchor.parent.appendChild(el);`,
84003
+ ` }`,
84004
+ ` }`,
84005
+ `}`
84006
+ ].join("\n");
84007
+ }
84008
+ /**
84009
+ * command-palette-portal-overlay phase — emit the per-element machinery for
84010
+ * ONE `r-portal="<expr>"` element:
84011
+ * - a `#roziePortal_<N>` template-ref attribute (returned for splicing
84012
+ * onto the open tag),
84013
+ * - a `viewChild<ElementRef>('roziePortal_<N>')` private field,
84014
+ * - the SHARED `__roziePortalPlace`/anchors/moved-set fields (once per
84015
+ * component — mirrors `emitSpreadBinding`'s `applyAttrsHelperDecl`
84016
+ * dedup),
84017
+ * - a `private __roziePortal_<N>_effect = effect(() => { ... });` field
84018
+ * initializer that guards `nativeElement` (Pitfall 7 — `viewChild()`
84019
+ * signals return `undefined` until the view is initialized, so a
84020
+ * pre-render effect tick is a safe no-op) and re-runs the placement
84021
+ * whenever the container expression's SIGNAL dependencies change
84022
+ * (the AOT-safe, signals-era analog of a reactive `$watch`).
84023
+ *
84024
+ * AOT-SAFE by construction: no `import.meta.url` (not referenced at all),
84025
+ * no inline template arrow (the container expression is a plain property/
84026
+ * method-call expression spliced into a `effect()` callback BODY — a
84027
+ * function statement, not a template-attribute arrow literal; analogjs AOT
84028
+ * rejects arrows INSIDE `{{ }}`/binding attribute VALUES, not inside a
84029
+ * class-field-initializer's own script-side callback).
84030
+ *
84031
+ * `effect()` in a field initializer is valid Angular injection context
84032
+ * (mirrors `emitSpreadBinding`/`emitListenerSpread`'s identical Pitfall-8
84033
+ * rationale) — no separate `ngAfterViewInit`/constructor wiring is needed,
84034
+ * unlike the r-keynav controller (which needs `viewChild()` to resolve
84035
+ * itself for the FIRST commit-timing-sensitive read; a portal's placement
84036
+ * has no such ordering hazard — `effect()`'s own no-op-until-mounted guard
84037
+ * covers the initial-render case identically to every render after it).
84038
+ */
84039
+ function emitPortalDirective(element, ctx) {
84040
+ const counter = ctx.injectionCounter ?? { next: 0 };
84041
+ const idx = counter.next++;
84042
+ const refName = `roziePortal_${idx}`;
84043
+ const effectFieldName = `__roziePortal_${idx}_effect`;
84044
+ const destroyRegisteredFieldName = `__roziePortal_${idx}_destroyRegistered`;
84045
+ const containerCode = rewriteListenerExpression(element.portalTo.expression, ctx.ir, {
84046
+ collisionRenames: ctx.collisionRenames,
84047
+ classMembers: ctx.classMembers,
84048
+ cvaModelProp: ctx.cvaModelProp,
84049
+ cvaMergeDisabled: ctx.cvaMergeDisabled
84050
+ });
84051
+ if (ctx.scriptInjections !== void 0) {
84052
+ ctx.scriptInjections.push({
84053
+ name: refName,
84054
+ decl: `private ${refName} = viewChild<ElementRef>('${refName}');`
84055
+ });
84056
+ if (!ctx.scriptInjections.some((si) => si.name === ROZIE_PORTAL_ANCHORS_FIELD_NAME)) {
84057
+ ctx.scriptInjections.push({
84058
+ name: ROZIE_PORTAL_ANCHORS_FIELD_NAME,
84059
+ decl: portalAnchorsFieldDecl()
84060
+ });
84061
+ ctx.scriptInjections.push({
84062
+ name: ROZIE_PORTAL_MOVED_FIELD_NAME,
84063
+ decl: portalMovedFieldDecl()
84064
+ });
84065
+ ctx.scriptInjections.push({
84066
+ name: ROZIE_PORTAL_PLACE_FIELD_NAME,
84067
+ decl: portalPlaceMethodDecl()
84068
+ });
84069
+ }
84070
+ ctx.scriptInjections.push({
84071
+ name: destroyRegisteredFieldName,
84072
+ decl: `private ${destroyRegisteredFieldName} = false;`
84073
+ });
84074
+ const effectDecl = [
84075
+ `private ${effectFieldName} = effect(() => {`,
84076
+ ` const el = this.${refName}()?.nativeElement;`,
84077
+ ` if (!el) return;`,
84078
+ ` this.${ROZIE_PORTAL_PLACE_FIELD_NAME}(el, ${containerCode});`,
84079
+ ` if (!this.${destroyRegisteredFieldName}) {`,
84080
+ ` this.${destroyRegisteredFieldName} = true;`,
84081
+ ` this.__rozieDestroyRef.onDestroy(() => {`,
84082
+ ` for (const moved of this.${ROZIE_PORTAL_MOVED_FIELD_NAME}) {`,
84083
+ ` moved.parentNode?.removeChild(moved);`,
84084
+ ` }`,
84085
+ ` });`,
84086
+ ` }`,
84087
+ `});`
84088
+ ].join("\n");
84089
+ ctx.scriptInjections.push({
84090
+ name: effectFieldName,
84091
+ decl: effectDecl
84092
+ });
84093
+ }
84094
+ if (ctx.hasListenerSpread !== void 0) ctx.hasListenerSpread.value = true;
84095
+ if (ctx.needsDestroyRefField !== void 0) ctx.needsDestroyRefField.value = true;
84096
+ return `#${refName}`;
84097
+ }
84098
+ /**
83209
84099
  * Emit a single attribute. Returns null when the attribute should be dropped
83210
84100
  * (e.g., r-html, which gets emitted later as `[innerHTML]="..."` by the
83211
84101
  * element emitter).
@@ -83252,6 +84142,7 @@ function emitSingleAttr(attr, ctx, elementTagName) {
83252
84142
  if (ctx.hasDisplayWrap) ctx.hasDisplayWrap.value = true;
83253
84143
  return `[${bindingName.startsWith("attr.") ? bindingName : `attr.${attr.name}`}]="rozieAttr(${expr})"`;
83254
84144
  }
84145
+ if (attr.name === "style" && isProvablyStringStyleExpression(attr.expression)) return `[attr.style]="${expr}"`;
83255
84146
  return `[${bindingName}]="${expr}"`;
83256
84147
  }
83257
84148
  if (attr.segments.length === 1 && attr.segments[0].kind === "binding") {
@@ -84429,6 +85320,21 @@ function emitElementInner$2(origNode, ctx) {
84429
85320
  if (attrText) partsHead.push(attrText);
84430
85321
  if (eventText) partsHead.push(eventText);
84431
85322
  for (const text of dynamicListenerTexts) partsHead.push(text);
85323
+ if (node.portalTo) {
85324
+ const portalRefText = emitPortalDirective(node, {
85325
+ ir: ctx.ir,
85326
+ collisionRenames: ctx.collisionRenames,
85327
+ loopBindings: ctx.loopBindings,
85328
+ scriptInjections: ctx.scriptInjections,
85329
+ injectionCounter: ctx.injectionCounter,
85330
+ hasListenerSpread: ctx.hasListenerSpread,
85331
+ needsDestroyRefField: ctx.needsDestroyRefField,
85332
+ cvaModelProp: ctx.cvaModelProp,
85333
+ cvaMergeDisabled: ctx.cvaMergeDisabled,
85334
+ classMembers: ctx.classMembers
85335
+ });
85336
+ partsHead.push(portalRefText);
85337
+ }
84432
85338
  const keynav = ctx.keynav ?? null;
84433
85339
  const keynavAttrs = [...keynavRootAttrs$2(keynav, node, ctx.ir), ...keynavItemAttrs$2(keynav, node, ctx.keynavItemIndexAlias ?? null, ctx.ir)];
84434
85340
  for (const a of keynavAttrs) partsHead.push(a);
@@ -86076,7 +86982,7 @@ function renderType$1(ann) {
86076
86982
  case "Boolean": return "boolean";
86077
86983
  case "Array": return "any[]";
86078
86984
  case "Object": return "Record<string, any>";
86079
- case "Function": return "(...args: unknown[]) => unknown";
86985
+ case "Function": return "(...args: any[]) => any";
86080
86986
  default: return ann.name;
86081
86987
  }
86082
86988
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -86086,7 +86992,7 @@ function renderType$1(ann) {
86086
86992
  if (ann.kind === "literal") {
86087
86993
  if (ann.value === "array") return "any[]";
86088
86994
  if (ann.value === "object") return "Record<string, any>";
86089
- if (ann.value === "function") return "(...args: unknown[]) => unknown";
86995
+ if (ann.value === "function") return "(...args: any[]) => any";
86090
86996
  return ann.value;
86091
86997
  }
86092
86998
  return "unknown";
@@ -86215,6 +87121,63 @@ function buildSetterCall$1(varName, operator, rhs) {
86215
87121
  return t$3.callExpression(t$3.identifier(setterName), [t$3.binaryExpression(binOp, t$3.callExpression(t$3.identifier(varName), []), rhs)]);
86216
87122
  }
86217
87123
  /**
87124
+ * 260712-ig6 Task B — detects the "null-widened prop spliced into a 3rd-party
87125
+ * object-literal call argument" shape: a `$props.<name>` read (where `<name>`
87126
+ * has `default: null`) that ultimately feeds an `ObjectProperty` VALUE inside
87127
+ * an `ObjectExpression` that is (possibly via nested object-literal property
87128
+ * values, e.g. `useEngine(el, { options: { group: $props.group } })`) itself
87129
+ * a DIRECT argument of a `CallExpression` — the mount-time engine-init shape
87130
+ * (`useSortableJS($refs.listEl, { options: { handle: $props.handle, ... } })`).
87131
+ *
87132
+ * Walks UP from the read, tolerating exactly two pass-through shapes on the
87133
+ * way to an `ObjectProperty` value:
87134
+ * - `ConditionalExpression` consequent/alternate (the `group` prop's
87135
+ * `cloneable ? { ... } : $props.group` ternary)
87136
+ * - nested `ObjectExpression` → `ObjectProperty` value chains (an object
87137
+ * literal nested inside another object literal's property value)
87138
+ *
87139
+ * Any OTHER parent shape (binary/logical expression, array literal, bare
87140
+ * statement, non-direct call argument, …) fails the match — this is
87141
+ * intentionally narrow, mirroring Pattern D's `isDirectCallArg` precedent.
87142
+ * Distinct from Pattern D: Pattern D fires on `$refs` DOM-ref reads that are
87143
+ * THEMSELVES a direct call argument; this fires on `$props` reads nested
87144
+ * inside an object-literal call argument.
87145
+ *
87146
+ * `excludeCallees` DISQUALIFIES the match when the terminal call's callee is
87147
+ * one of the emitter's OWN synthesized reactive setters (`set<Data>` /
87148
+ * `set<ModelProp>`, minted by `buildSetterCall` for `$data.x = {...}` /
87149
+ * `$model.x = {...}` writes). Those setter calls are Rozie-internal — the
87150
+ * `{...}` argument feeds a Solid `createSignal` setter, not a 3rd-party lib's
87151
+ * typed options object, so a null-widened prop spliced into one must stay
87152
+ * `null` verbatim (the cross-target `PropDefaultCoercion` conformance probe
87153
+ * asserts `JSON.stringify({ a: null, ... })` renders the literal `"a":null`
87154
+ * substring on every target — `?? undefined` would DROP the key entirely,
87155
+ * a real cross-target behavior regression, not a type-only change).
87156
+ */
87157
+ function isNullWidenedPropObjectLiteralCallArgTarget(path, excludeCallees) {
87158
+ let cur = path;
87159
+ for (;;) {
87160
+ const parent = cur.parentPath;
87161
+ if (!parent) return false;
87162
+ if (parent.isConditionalExpression() && (parent.node.consequent === cur.node || parent.node.alternate === cur.node)) {
87163
+ cur = parent;
87164
+ continue;
87165
+ }
87166
+ if (parent.isObjectProperty() && parent.node.value === cur.node) {
87167
+ const objExpr = parent.parentPath;
87168
+ if (!objExpr || !objExpr.isObjectExpression()) return false;
87169
+ cur = objExpr;
87170
+ continue;
87171
+ }
87172
+ if (parent.isCallExpression() && parent.node.arguments.some((arg) => arg === cur.node)) {
87173
+ const callee = parent.node.callee;
87174
+ if (t$3.isIdentifier(callee) && excludeCallees.has(callee.name)) return false;
87175
+ return true;
87176
+ }
87177
+ return false;
87178
+ }
87179
+ }
87180
+ /**
86218
87181
  * Phase 18 (Req 2) — normalize the producer-side two-way-write sigil `$model`
86219
87182
  * to `$props` across a cloned File, in place. See the call-site comment in
86220
87183
  * `rewriteRozieIdentifiers` for the full contract; `$model.X` is model-only and
@@ -86321,7 +87284,7 @@ function hoistPolymorphicModelGuards(cloned, polymorphicModelProps) {
86321
87284
  *
86322
87285
  * @experimental — shape may change before v1.0
86323
87286
  */
86324
- function rewriteRozieExpressionNode(expr, ir) {
87287
+ function rewriteRozieExpressionNode(expr, ir, options) {
86325
87288
  let programBody;
86326
87289
  const isBlock = t$3.isBlockStatement(expr);
86327
87290
  if (isBlock) programBody = t$3.cloneNode(expr, true, false).body;
@@ -86335,7 +87298,7 @@ function rewriteRozieExpressionNode(expr, ir) {
86335
87298
  sourceType: "module"
86336
87299
  },
86337
87300
  comments: []
86338
- }, ir).rewrittenProgram.program.body;
87301
+ }, ir, options).rewrittenProgram.program.body;
86339
87302
  if (isBlock) return t$3.blockStatement(body);
86340
87303
  if (body.length === 1 && t$3.isExpressionStatement(body[0])) return body[0].expression;
86341
87304
  const last = body[body.length - 1];
@@ -86346,10 +87309,12 @@ function rewriteRozieExpressionNode(expr, ir) {
86346
87309
  * Full Solid identifier rewrite pass. Replaces all $props/$data/$refs/$emit
86347
87310
  * references with their Solid-idiomatic equivalents.
86348
87311
  */
86349
- function rewriteRozieIdentifiers(cloned, ir) {
87312
+ function rewriteRozieIdentifiers(cloned, ir, options) {
86350
87313
  const diagnostics = [];
87314
+ const nonNullRefCallArgs = options?.nonNullRefCallArgs === true;
86351
87315
  const modelProps = new Set(ir.props.filter((p) => p.isModel).map((p) => p.name));
86352
87316
  const nonModelProps = new Set(ir.props.filter((p) => !p.isModel).map((p) => p.name));
87317
+ const nullWidenedNonModelProps = new Set(ir.props.filter((p) => !p.isModel && t$3.isNullLiteral(p.defaultValue)).map((p) => p.name));
86353
87318
  const unknownModelProps = new Set(ir.props.filter((p) => p.isModel && renderType$1(p.typeAnnotation) === "unknown").map((p) => p.name));
86354
87319
  const polymorphicModelProps = new Set(ir.props.filter((p) => p.isModel && (p.typeAnnotation.kind === "union" || renderType$1(p.typeAnnotation) === "unknown")).map((p) => p.name));
86355
87320
  const dataNames = new Set(ir.state.map((s) => s.name));
@@ -86466,6 +87431,12 @@ function rewriteRozieIdentifiers(cloned, ir) {
86466
87431
  return;
86467
87432
  }
86468
87433
  if (nonModelProps.has(property.name)) {
87434
+ if (nonNullRefCallArgs && nullWidenedNonModelProps.has(property.name) && isNullWidenedPropObjectLiteralCallArgTarget(path, solidSetters)) {
87435
+ const localRead = t$3.memberExpression(t$3.identifier("local"), t$3.identifier(property.name));
87436
+ path.replaceWith(t$3.logicalExpression("??", localRead, t$3.identifier("undefined")));
87437
+ path.skip();
87438
+ return;
87439
+ }
86469
87440
  path.node.object = t$3.identifier("local");
86470
87441
  return;
86471
87442
  }
@@ -86479,7 +87450,8 @@ function rewriteRozieIdentifiers(cloned, ir) {
86479
87450
  }
86480
87451
  if (object.name === "$refs" && refNames.has(property.name)) {
86481
87452
  const refIdent = t$3.identifier(property.name + "Ref");
86482
- if (property.name === "__rozieRoot") path.replaceWith(t$3.tsNonNullExpression(refIdent));
87453
+ const isDirectCallArg = nonNullRefCallArgs && path.parentPath?.isCallExpression() && path.parentPath.node.arguments.includes(path.node);
87454
+ if (property.name === "__rozieRoot" || isDirectCallArg) path.replaceWith(t$3.tsNonNullExpression(refIdent));
86483
87455
  else path.replaceWith(refIdent);
86484
87456
  path.skip();
86485
87457
  return;
@@ -86860,6 +87832,19 @@ function genCode$1(node) {
86860
87832
  return generate$7(node, GEN_OPTS$6).code;
86861
87833
  }
86862
87834
  /**
87835
+ * Quick task 260714-orv — render hoisted user imports in ONE
87836
+ * @babel/generator pass so a comment shared between two adjacent imports
87837
+ * (Babel attaches it as BOTH the earlier import's `trailingComments` AND the
87838
+ * later import's `leadingComments`) prints exactly once. Generating imports
87839
+ * one at a time (`nodes.map(genCode).join`) gives each import its OWN
87840
+ * comment-dedup set, doubling any shared comment. `t.program(nodes)` prints
87841
+ * only its body statements (no wrapping braces/`File` boilerplate), so
87842
+ * non-comment cases stay byte-identical.
87843
+ */
87844
+ function genImportsBlock(nodes) {
87845
+ return generate$7(t$3.program(nodes), GEN_OPTS$6).code;
87846
+ }
87847
+ /**
86863
87848
  * ROZ-cast-blindness fix — Solid emits each ComputedDecl's body via
86864
87849
  * `rewriteNode(c.body, ir)` directly from IR (never re-scanning the cloned
86865
87850
  * Program for the body itself), so there is no existing "find the cloned
@@ -86918,6 +87903,17 @@ function capitalize$6(name) {
86918
87903
  return name.charAt(0).toUpperCase() + name.slice(1);
86919
87904
  }
86920
87905
  /**
87906
+ * 260712-kl1 — exact-AST-shape gate mirroring the `isMutableLiteralFactoryDefault`
87907
+ * / `isNullWidenedPropObjectLiteralCallArgTarget` predicate convention. Matches
87908
+ * ONLY a Babel `NullLiteral` prop default (`default: null`) — never `undefined`,
87909
+ * `void 0`, or any other falsy/nullish shape. Used to widen a model prop's
87910
+ * `createControllableSignal<T>` generic to `T | null` (see 260712-kb9's
87911
+ * NEW-controllable-null-default catalog entry).
87912
+ */
87913
+ function isNullLiteralDefault(dv) {
87914
+ return t$3.isNullLiteral(dv);
87915
+ }
87916
+ /**
86921
87917
  * WR-01 fix (73-REVIEW.md, 73-10 gap-closure): collect every Identifier
86922
87918
  * binding name introduced by a declarator's `id` pattern — Identifier
86923
87919
  * directly, or recursively through ObjectPattern / ArrayPattern /
@@ -87130,7 +88126,7 @@ function emitScript$1(ir, collectors, _registry) {
87130
88126
  diagnostics.push(...rewriteResult.diagnostics);
87131
88127
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts } = partitionUserImports$1(rewriteResult.rewrittenProgram);
87132
88128
  rewriteResult.rewrittenProgram.program.body = bodyStmts;
87133
- const userImports = userImportNodes.length > 0 ? userImportNodes.map((imp) => genCode$1(imp)).join("\n") + "\n" : "";
88129
+ const userImports = userImportNodes.length > 0 ? genImportsBlock(userImportNodes) + "\n" : "";
87134
88130
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => genCode$1(decl));
87135
88131
  const contextEmit = emitContext$1(ir, {
87136
88132
  solid: collectors.solidImports,
@@ -87146,7 +88142,9 @@ function emitScript$1(ir, collectors, _registry) {
87146
88142
  const dv = p.defaultValue;
87147
88143
  const raw = genCode$1(dv);
87148
88144
  const val = isMutableLiteralFactoryDefault(dv) ? `(${raw})()` : raw;
87149
- return `${p.name}: ${val}`;
88145
+ const ann = p.typeAnnotation;
88146
+ const entryVal = isMutableLiteralFactoryDefault(dv) && ann.kind === "identifier" && (ann.name === "Object" || ann.name === "Array") ? `${val} as ${renderType$1(ann)}` : val;
88147
+ return `${p.name}: ${entryVal}`;
87150
88148
  }).join(", ")} }, _props);\n`;
87151
88149
  }
87152
88150
  for (const p of ir.props) {
@@ -87160,7 +88158,8 @@ function emitScript$1(ir, collectors, _registry) {
87160
88158
  else dflt = raw;
87161
88159
  }
87162
88160
  const tsType = renderType$1(p.typeAnnotation);
87163
- hookLines.push(`const [${p.name}, ${setterName}] = createControllableSignal<${tsType}>(_props as unknown as Record<string, unknown>, '${p.name}', ${dflt});`);
88161
+ const genericType = isNullLiteralDefault(p.defaultValue) && tsType !== "unknown" ? `${tsType} | null` : tsType;
88162
+ hookLines.push(`const [${p.name}, ${setterName}] = createControllableSignal<${genericType}>(_props as unknown as Record<string, unknown>, '${p.name}', ${dflt});`);
87164
88163
  }
87165
88164
  for (const s of ir.state) {
87166
88165
  collectors.solidImports.add("createSignal");
@@ -87192,7 +88191,7 @@ function emitScript$1(ir, collectors, _registry) {
87192
88191
  for (const lh of ir.lifecycle) if (lh.phase === "mount") if (lh.cleanup) {
87193
88192
  collectors.solidImports.add("onMount");
87194
88193
  collectors.solidImports.add("onCleanup");
87195
- const rewrittenSetup = rewriteRozieExpressionNode(lh.setup, ir);
88194
+ const rewrittenSetup = rewriteRozieExpressionNode(lh.setup, ir, { nonNullRefCallArgs: true });
87196
88195
  const rewrittenCleanup = rewriteRozieExpressionNode(lh.cleanup, ir);
87197
88196
  if (t$3.isBlockStatement(lh.setup) && cleanupReferencesSetupLocal(lh.setup, lh.cleanup)) {
87198
88197
  const setupStatements = rewrittenSetup.body;
@@ -87206,7 +88205,7 @@ function emitScript$1(ir, collectors, _registry) {
87206
88205
  }
87207
88206
  } else {
87208
88207
  collectors.solidImports.add("onMount");
87209
- const arg = lifecycleArg(rewriteRozieExpressionNode(lh.setup, ir));
88208
+ const arg = lifecycleArg(rewriteRozieExpressionNode(lh.setup, ir, { nonNullRefCallArgs: true }));
87210
88209
  hookLines.push(`onMount(${arg});`);
87211
88210
  }
87212
88211
  else if (lh.phase === "unmount") {
@@ -89881,7 +90880,22 @@ function emitLoop$1(node, ctx) {
89881
90880
  invokeAccessors: ctx.invokeAccessors,
89882
90881
  loopValueBindings: new Set([...ctx.loopValueBindings ?? [], node.itemAlias])
89883
90882
  });
89884
- return `<Key each={${iterableCode} as readonly any[]} by={(${node.itemAlias}) => ${keyCode}}>{${aliasStr} => ${bodyJsx}}</Key>`;
90883
+ return `<Key each={${new Set([
90884
+ "Identifier",
90885
+ "MemberExpression",
90886
+ "OptionalMemberExpression",
90887
+ "CallExpression",
90888
+ "OptionalCallExpression",
90889
+ "NewExpression",
90890
+ "ArrayExpression",
90891
+ "ObjectExpression",
90892
+ "TSNonNullExpression",
90893
+ "TSAsExpression",
90894
+ "ParenthesizedExpression",
90895
+ "StringLiteral",
90896
+ "NumericLiteral",
90897
+ "TemplateLiteral"
90898
+ ]).has(node.iterableExpression.type) ? iterableCode : `(${iterableCode})`} as readonly any[]} by={(${node.itemAlias}) => ${keyCode}}>{${aliasStr} => ${bodyJsx}}</Key>`;
89885
90899
  }
89886
90900
  /**
89887
90901
  * Find an attribute by name.
@@ -90227,8 +91241,34 @@ function mergeEventAttributes(attrsJsx, eventsJsx, elementTag) {
90227
91241
  * (so the child is never hidden) while still changing value — and thus
90228
91242
  * still triggering `keyed` recreation — whenever the underlying key changes.
90229
91243
  */
91244
+ /**
91245
+ * command-palette-portal-overlay phase — `r-portal="<expr>"` element
91246
+ * teleport. Wraps the element markup in Solid's native `<Show when={c}
91247
+ * fallback={tree}><Portal mount={c}>{tree}</Portal></Show>` — falsy
91248
+ * container renders `tree` in place via the `fallback` prop (never mounts
91249
+ * `<Portal>` at all), matching React's `container ? createPortal(tree,
91250
+ * container) : tree` and Vue's `<Teleport :disabled>` in-place semantics.
91251
+ *
91252
+ * SSR guard: `typeof document === 'undefined'` short-circuits the container
91253
+ * expression to `null` BEFORE it is evaluated at all — some author
91254
+ * expressions call `document.querySelector` internally (see
91255
+ * `resolveAppendTo`/`resolveTo`), so the guard must gate evaluation, not
91256
+ * just the result.
91257
+ */
91258
+ function emitPortalWrap(node, markup, ctx) {
91259
+ const { expression } = node.portalTo;
91260
+ if (ctx.elementPortalImport) ctx.elementPortalImport.needed = true;
91261
+ const containerCode = `(typeof document === 'undefined' ? null : (${rewriteTemplateExpression$1(expression, ctx.ir, {
91262
+ invokeAccessors: ctx.invokeAccessors,
91263
+ loopValueBindings: ctx.loopValueBindings,
91264
+ scopeAccessorParams: ctx.scopeAccessorParams
91265
+ })}))`;
91266
+ ctx.collectors.solid.add("Show");
91267
+ return `<Show when={${containerCode}} fallback={${markup}}><Portal mount={${containerCode}}>${markup}</Portal></Show>`;
91268
+ }
90230
91269
  function emitElement$1(origNode, ctx) {
90231
91270
  const markup = emitElementInner$1(origNode, ctx);
91271
+ if (origNode.portalTo) return emitPortalWrap(origNode, markup, ctx);
90232
91272
  if ((origNode.tagKind === "component" || origNode.tagKind === "self") && origNode.remountKeyExpression) {
90233
91273
  const keyExprCode = rewriteTemplateExpression$1(origNode.remountKeyExpression, ctx.ir, {
90234
91274
  invokeAccessors: ctx.invokeAccessors,
@@ -90466,11 +91506,13 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90466
91506
  jsx: "null",
90467
91507
  scriptInjections: [],
90468
91508
  needsKeyedImport: false,
90469
- diagnostics: []
91509
+ diagnostics: [],
91510
+ hasElementPortal: false
90470
91511
  };
90471
91512
  const scriptInjections = [];
90472
91513
  const injectionCounter = { next: 0 };
90473
91514
  const keyedImport = { needed: false };
91515
+ const elementPortalImport = { needed: false };
90474
91516
  const keynav = resolveKeynavPlan$1(ir);
90475
91517
  const ctx = {
90476
91518
  ir,
@@ -90480,6 +91522,7 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90480
91522
  scriptInjections,
90481
91523
  injectionCounter,
90482
91524
  keyedImport,
91525
+ elementPortalImport,
90483
91526
  keynav,
90484
91527
  ...opts.scopeAttr !== void 0 ? { scopeAttr: opts.scopeAttr } : {}
90485
91528
  };
@@ -90489,7 +91532,8 @@ function emitTemplate$1(ir, collectors, registry, opts = {}) {
90489
91532
  jsx,
90490
91533
  scriptInjections,
90491
91534
  needsKeyedImport: keyedImport.needed,
90492
- diagnostics
91535
+ diagnostics,
91536
+ hasElementPortal: elementPortalImport.needed
90493
91537
  };
90494
91538
  }
90495
91539
  //#endregion
@@ -91458,7 +92502,7 @@ function emitSolid(ir, opts = {}) {
91458
92502
  ...listenersResult.scriptInjections
91459
92503
  ].filter((s) => s.trim().length > 0).join("\n\n");
91460
92504
  const solidImportsStr = `import type { JSX } from 'solid-js';\n` + solidImports.render();
91461
- const portalImport = scriptResult.hasPortals ? "import { render } from 'solid-js/web';\n" : "";
92505
+ const portalImport = (scriptResult.hasPortals ? "import { render } from 'solid-js/web';\n" : "") + (templateResult.hasElementPortal ? "import { Portal } from 'solid-js/web';\n" : "");
91462
92506
  const keyedImport = templateResult.needsKeyedImport ? "import { Key } from '@solid-primitives/keyed';\n" : "";
91463
92507
  const shell = buildShell$1({
91464
92508
  componentName: ir.name,
@@ -92950,7 +93994,7 @@ function renderTsType(ann) {
92950
93994
  case "Boolean": return "boolean";
92951
93995
  case "Array": return "any[]";
92952
93996
  case "Object": return "any";
92953
- case "Function": return "((...args: unknown[]) => unknown) | null";
93997
+ case "Function": return "((...args: any[]) => any) | null";
92954
93998
  default: return ann.name;
92955
93999
  }
92956
94000
  if (ann.kind === "union") return ann.members.map((m) => {
@@ -92979,14 +94023,81 @@ function renderTsType(ann) {
92979
94023
  * The `!`-shape matches what Rozie already emits for Angular `@Input` and Lit
92980
94024
  * `@query` ref fields.
92981
94025
  */
94026
+ /**
94027
+ * 260712-a09 (Pattern B) — an explicit `default: undefined` (or `default: void 0`)
94028
+ * is authorial shorthand for "no meaningful default", not a real default value.
94029
+ * `prop.defaultValue` is a live (non-null) AST node for either form, so the
94030
+ * `prop.defaultValue != null` guard below would otherwise route it into the
94031
+ * `= <default>` initializer branch and emit `foo: string = undefined` — a
94032
+ * TS2322 under strictNullChecks (a typed field can't be initialized with
94033
+ * `undefined` unless the type itself admits it). Detect both spellings here
94034
+ * and treat them identically to "no default" so they fall through to the
94035
+ * existing `?:`/`!:` branch.
94036
+ */
94037
+ function isUndefinedDefault(expr) {
94038
+ return t$3.isIdentifier(expr, { name: "undefined" }) || t$3.isUnaryExpression(expr) && expr.operator === "void";
94039
+ }
94040
+ /**
94041
+ * 260712-ig6 Task A — the full ARIAMixin field-name set (lib.dom.d.ts). A
94042
+ * prop authored with one of these names collides with LitElement's own
94043
+ * `implements ARIAMixin` field declaration (always `<T> | null`, NEVER
94044
+ * `undefined`-admitting) if it lands in the ordinary no-default `?:`/`!:`
94045
+ * branch below — Pattern B's `default: undefined` routes there and would
94046
+ * otherwise emit `ariaLabel?: string;` (`string | undefined`), which is not
94047
+ * assignable to the base class's `string | null` field (TS2416/TS1240).
94048
+ * Gated STRICTLY on name-set membership so every other prop name keeps its
94049
+ * pre-existing byte-identical `?:`/`!:` output.
94050
+ */
94051
+ const ARIA_MIXIN_FIELDS = new Set([
94052
+ "ariaAtomic",
94053
+ "ariaAutoComplete",
94054
+ "ariaBusy",
94055
+ "ariaChecked",
94056
+ "ariaColCount",
94057
+ "ariaColIndex",
94058
+ "ariaColSpan",
94059
+ "ariaCurrent",
94060
+ "ariaDescription",
94061
+ "ariaDisabled",
94062
+ "ariaExpanded",
94063
+ "ariaHasPopup",
94064
+ "ariaHidden",
94065
+ "ariaInvalid",
94066
+ "ariaKeyShortcuts",
94067
+ "ariaLabel",
94068
+ "ariaLevel",
94069
+ "ariaLive",
94070
+ "ariaModal",
94071
+ "ariaMultiLine",
94072
+ "ariaMultiSelectable",
94073
+ "ariaOrientation",
94074
+ "ariaPlaceholder",
94075
+ "ariaPosInSet",
94076
+ "ariaPressed",
94077
+ "ariaReadOnly",
94078
+ "ariaRequired",
94079
+ "ariaRoleDescription",
94080
+ "ariaRowCount",
94081
+ "ariaRowIndex",
94082
+ "ariaRowSpan",
94083
+ "ariaSelected",
94084
+ "ariaSetSize",
94085
+ "ariaSort",
94086
+ "ariaValueMax",
94087
+ "ariaValueMin",
94088
+ "ariaValueNow",
94089
+ "ariaValueText",
94090
+ "role"
94091
+ ]);
92982
94092
  function renderFieldSuffix(prop) {
92983
94093
  const tsType = renderTsType(prop.typeAnnotation);
92984
- if (prop.defaultValue != null) {
94094
+ if (prop.defaultValue != null && !isUndefinedDefault(prop.defaultValue)) {
92985
94095
  let fieldType = tsType;
92986
94096
  const admitsNull = /\bnull\b/.test(fieldType) || /(^|\|)\s*(any|unknown)\s*(\||$)/.test(fieldType);
92987
94097
  if (t$3.isNullLiteral(prop.defaultValue) && !admitsNull) fieldType = `${fieldType} | null`;
92988
94098
  return `: ${fieldType} = ${renderDefault(prop.defaultValue, prop.typeAnnotation)}`;
92989
94099
  }
94100
+ if (ARIA_MIXIN_FIELDS.has(prop.name)) return `: ${/\bnull\b/.test(tsType) || /(^|\|)\s*(any|unknown)\s*(\||$)/.test(tsType) ? tsType : `${tsType} | null`} = null`;
92990
94101
  return prop.required ? `!: ${tsType}` : `?: ${tsType}`;
92991
94102
  }
92992
94103
  function isPrimitiveType(ann) {
@@ -93049,25 +94160,35 @@ function emitStateField(stateName, init) {
93049
94160
  else if (t$3.isObjectExpression(init) && init.properties.length === 0) typeArg = "<any>";
93050
94161
  return ` private _${stateName} = signal${typeArg}(${renderExpression(init)});`;
93051
94162
  }
93052
- function emitRefField(refName, elementTag, composedType) {
94163
+ function emitRefField(refName, elementTag, composedType, hasElementPortal) {
93053
94164
  const field = `_ref${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
93054
- if (composedType) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${composedType};`;
93055
- let domType = "HTMLElement";
93056
- switch (elementTag.toLowerCase()) {
93057
- case "dialog":
93058
- domType = "HTMLDialogElement";
93059
- break;
93060
- case "img":
93061
- domType = "HTMLImageElement";
93062
- break;
93063
- case "ul":
93064
- domType = "HTMLUListElement";
93065
- break;
93066
- case "li":
93067
- domType = "HTMLLIElement";
93068
- break;
94165
+ let domType;
94166
+ if (composedType) domType = composedType;
94167
+ else {
94168
+ domType = "HTMLElement";
94169
+ switch (elementTag.toLowerCase()) {
94170
+ case "dialog":
94171
+ domType = "HTMLDialogElement";
94172
+ break;
94173
+ case "img":
94174
+ domType = "HTMLImageElement";
94175
+ break;
94176
+ case "ul":
94177
+ domType = "HTMLUListElement";
94178
+ break;
94179
+ case "li":
94180
+ domType = "HTMLLIElement";
94181
+ break;
94182
+ }
93069
94183
  }
93070
- return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94184
+ if (!hasElementPortal) return ` @query('[data-rozie-ref="${refName}"]') private ${field}!: ${domType};`;
94185
+ const rawField = `__rozieRawRef${refName.charAt(0).toUpperCase()}${refName.slice(1)}`;
94186
+ return [
94187
+ ` @query('[data-rozie-ref="${refName}"]') private ${rawField}!: ${domType};`,
94188
+ ` private get ${field}(): ${domType} {`,
94189
+ ` return rozieResolvePortalledRef(this, '[data-rozie-ref="${refName}"]', this.${rawField}) as ${domType};`,
94190
+ " }"
94191
+ ].join("\n");
93071
94192
  }
93072
94193
  function isLifecycleCall(stmt) {
93073
94194
  if (!t$3.isExpressionStatement(stmt)) return { isHook: false };
@@ -93353,18 +94474,22 @@ function emitScript(ir, opts) {
93353
94474
  const composedTypeImports = /* @__PURE__ */ new Set();
93354
94475
  for (const ref of ir.refs) {
93355
94476
  const composedType = composedRefs.get(ref.name);
93356
- fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType));
94477
+ fieldLines.push(emitRefField(ref.name, ref.elementTag, composedType, opts.hasElementPortal));
93357
94478
  if (composedType && composedType !== ir.name) composedTypeImports.add(composedType);
93358
94479
  }
94480
+ if (opts.hasElementPortal && ir.refs.length > 0) opts.runtime.add("rozieResolvePortalledRef");
93359
94481
  const composedTypeImportLines = [];
93360
94482
  for (const typeName of composedTypeImports) {
93361
94483
  const decl = (ir.components ?? []).find((c) => c.localName === typeName);
93362
- if (decl) composedTypeImportLines.push(`import type { ${typeName} } from '${decl.importPath}';`);
94484
+ if (decl) {
94485
+ const importPath = isPublishedSpecifier(decl.importPath) ? rewriteRozieImport(decl.importPath, "lit") : decl.importPath;
94486
+ composedTypeImportLines.push(`import type { ${typeName} } from '${importPath}';`);
94487
+ }
93363
94488
  }
93364
94489
  const rewritten = rewriteScript(ir.setupBody.scriptProgram, ir, { runtime: opts.runtime });
93365
94490
  const { userImports: userImportNodes, hoistedTypeDecls: hoistedTypeNodes, bodyStmts: nonImportStmts } = partitionUserImports(rewritten.file);
93366
94491
  rewritten.file.program.body = nonImportStmts;
93367
- 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" : "");
94492
+ const userImports = (composedTypeImportLines.length > 0 ? composedTypeImportLines.join("\n") + "\n" : "") + (userImportNodes.length > 0 ? generate$1(t$3.program(userImportNodes), GEN_OPTS$1).code + "\n" : "");
93368
94493
  const hoistedTypeDecls = hoistedTypeNodes.map((decl) => generate$1(decl, GEN_OPTS$1).code);
93369
94494
  const partition = partitionScript(rewritten.file);
93370
94495
  const computedNames = new Set(ir.computed.map((c) => c.name));
@@ -95092,6 +96217,7 @@ function emitElementOpenTag(node, ir, irName, opts) {
95092
96217
  const tagName = resolveTagName(node, irName);
95093
96218
  const parts = [];
95094
96219
  let refAttr = null;
96220
+ let portalSentinel = "";
95095
96221
  const rModelAttr = node.attributes.find((a) => attributeIsRModel(a));
95096
96222
  const staticClassValues = [];
95097
96223
  let bindingClass = null;
@@ -95194,16 +96320,27 @@ function emitElementOpenTag(node, ir, irName, opts) {
95194
96320
  parts.push(`\${rozieListeners(${expr})}`);
95195
96321
  }
95196
96322
  if (refAttr) parts.push(refAttr);
96323
+ if (node.portalTo && opts._state) {
96324
+ const fieldName = `__roziePortal${opts._state.portalCount++}`;
96325
+ parts.push(`data-rozie-portal-ref="${fieldName}"`);
96326
+ portalSentinel = `<span data-rozie-portal-anchor="${fieldName}" hidden></span>`;
96327
+ opts.decorators.add("query");
96328
+ opts.runtime.add("RoziePortalController");
96329
+ const containerCode = rewriteTemplateExpression(node.portalTo.expression, ir);
96330
+ opts._state.portalFieldDecls.push(` @query('[data-rozie-portal-ref="${fieldName}"]') private ${fieldName}!: HTMLElement;`);
96331
+ opts._state.portalFieldDecls.push(` @query('[data-rozie-portal-anchor="${fieldName}"]') private ${fieldName}Anchor!: HTMLElement;`);
96332
+ opts._state.portalFieldDecls.push(` private ${fieldName}Controller = new RoziePortalController(this, () => this.${fieldName}, () => this.${fieldName}Anchor, () => (${containerCode}));`);
96333
+ }
95197
96334
  const keynav = opts.keynav ?? null;
95198
96335
  parts.push(...keynavRootAttrs(keynav, node, opts.runtime), ...keynavItemAttrs(keynav, node, opts.keynavItemIndexAlias ?? null));
95199
96336
  if ((node.tagKind === "html" || node.tagKind === "component") && opts.scopeHash) parts.push(`data-rozie-s-${opts.scopeHash}`);
95200
96337
  const attrsText = parts.length > 0 ? " " + parts.join(" ") : "";
95201
96338
  if (isVoidElement(node.tagName) && node.children.length === 0) return {
95202
- open: `<${tagName}${attrsText} />`,
96339
+ open: `${portalSentinel}<${tagName}${attrsText} />`,
95203
96340
  selfClose: true
95204
96341
  };
95205
96342
  return {
95206
- open: `<${tagName}${attrsText}>`,
96343
+ open: `${portalSentinel}<${tagName}${attrsText}>`,
95207
96344
  selfClose: false
95208
96345
  };
95209
96346
  }
@@ -95472,6 +96609,8 @@ function emitTemplate(ir, opts) {
95472
96609
  slotFillerClassFields: [],
95473
96610
  slotFillerUpdatedBody: [],
95474
96611
  slotFillerDisconnectReset: [],
96612
+ portalFieldDecls: [],
96613
+ portalCount: 0,
95475
96614
  diagnostics
95476
96615
  };
95477
96616
  const keynav = resolveKeynavPlan(ir);
@@ -95500,6 +96639,7 @@ function emitTemplate(ir, opts) {
95500
96639
  slotFillerUpdatedBody: state.slotFillerUpdatedBody,
95501
96640
  slotFillerDisconnectReset: state.slotFillerDisconnectReset,
95502
96641
  keynavFieldDecls,
96642
+ portalFieldDecls: state.portalFieldDecls,
95503
96643
  diagnostics
95504
96644
  };
95505
96645
  }
@@ -95522,6 +96662,7 @@ function emitTemplate(ir, opts) {
95522
96662
  slotFillerUpdatedBody: state.slotFillerUpdatedBody,
95523
96663
  slotFillerDisconnectReset: state.slotFillerDisconnectReset,
95524
96664
  keynavFieldDecls,
96665
+ portalFieldDecls: state.portalFieldDecls,
95525
96666
  diagnostics
95526
96667
  };
95527
96668
  }
@@ -95974,6 +97115,23 @@ function stringifyRules(rules, source) {
95974
97115
  function escapeCssForTemplateLiteral(css) {
95975
97116
  return css.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
95976
97117
  }
97118
+ /**
97119
+ * FNV-1a 32-bit hash, hex-encoded. Local copy — INTENTIONALLY not shared
97120
+ * with `scopeHash.ts`'s `fnv1a32Hex` (quick 260716-npt Finding 1): that one
97121
+ * hashes `basename::componentName` (name-derived), so two versions of the
97122
+ * same source file collide identically on it. This hash is over the
97123
+ * injected CSS TEXT ITSELF, so distinct global CSS payloads produce
97124
+ * distinct `injectGlobalStyles` runtime dedup ids even when the
97125
+ * componentName is unchanged.
97126
+ */
97127
+ function fnv1a32HexOfCss(s) {
97128
+ let h = 2166136261;
97129
+ for (let i = 0; i < s.length; i++) {
97130
+ h ^= s.charCodeAt(i);
97131
+ h = Math.imul(h, 16777619) >>> 0;
97132
+ }
97133
+ return h.toString(16).padStart(8, "0");
97134
+ }
95977
97135
  function emitStyle(styles, source, opts) {
95978
97136
  const diagnostics = [];
95979
97137
  const scopedRules = styles.scopedRules;
@@ -95982,7 +97140,11 @@ function emitStyle(styles, source, opts) {
95982
97140
  const engineCss = stringifyRules((styles.engineRules ?? []).flatMap((r) => r.children ?? []), source);
95983
97141
  const rawScopedCss = stringifyRules(scopedRules, source);
95984
97142
  const scopedCss = opts.scopeHash ? scopeCss(rawScopedCss, opts.scopeHash) : rawScopedCss;
95985
- const globalCss = [rootRules.length > 0 ? stringifyRules(rootRules, source) : "", engineCss].filter((s) => s.length > 0).join("\n");
97143
+ const globalCss = [
97144
+ rootRules.length > 0 ? stringifyRules(rootRules, source) : "",
97145
+ engineCss,
97146
+ opts.hasElementPortal === true ? scopedCss : ""
97147
+ ].filter((s) => s.length > 0).join("\n");
95986
97148
  const combinedScoped = [
95987
97149
  HOST_DISPLAY_DEFAULT,
95988
97150
  scopedCss,
@@ -95995,7 +97157,8 @@ function emitStyle(styles, source, opts) {
95995
97157
  if (globalCss.length > 0) {
95996
97158
  opts.runtime.add("injectGlobalStyles");
95997
97159
  const escaped = escapeCssForTemplateLiteral(globalCss);
95998
- globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-global`}', \`\n${escaped}\n\`);`;
97160
+ const cssHash = fnv1a32HexOfCss(globalCss);
97161
+ globalStyleCall = `injectGlobalStyles('${`rozie-${toKebabCase(opts.componentName)}-${cssHash}-global`}', \`\n${escaped}\n\`);`;
95999
97162
  }
96000
97163
  return {
96001
97164
  staticStylesField,
@@ -96059,6 +97222,33 @@ function buildShell(parts) {
96059
97222
  }
96060
97223
  //#endregion
96061
97224
  //#region ../targets/lit/src/emitLit.ts
97225
+ /**
97226
+ * command-palette-portal-overlay phase — a cheap, INDEPENDENT presence-only
97227
+ * walk over `ir.template` for at least one `r-portal` element. Needed
97228
+ * because `emitStyle` runs BEFORE `emitTemplate` in this file's call
97229
+ * sequence (styles must be ready before the template walk references
97230
+ * `opts.scopeHash`), so the template-walk-derived `portalCount` isn't
97231
+ * available yet at the point `emitStyle` needs to know whether to ALSO
97232
+ * push `scopedCss` through `injectGlobalStyles` (see `emitStyle.ts`'s
97233
+ * `opts.hasElementPortal` doc comment / `RoziePortalController.ts`'s module
97234
+ * doc comment for WHY). A second, tiny boolean-only walk is simpler and
97235
+ * lower-risk than reordering the two emit passes or synchronizing two
97236
+ * independent per-element counters.
97237
+ */
97238
+ function hasElementPortal(node) {
97239
+ if (node === null) return false;
97240
+ switch (node.type) {
97241
+ case "TemplateElement":
97242
+ if (node.portalTo !== void 0) return true;
97243
+ return node.children.some(hasElementPortal);
97244
+ case "TemplateFragment": return node.children.some(hasElementPortal);
97245
+ case "TemplateConditional": return node.branches.some((b) => b.body.some(hasElementPortal));
97246
+ case "TemplateMatch": return node.branches.some((b) => b.body.some(hasElementPortal)) || node.hostElement !== void 0 && hasElementPortal(node.hostElement);
97247
+ case "TemplateLoop": return node.body.some(hasElementPortal);
97248
+ case "TemplateSlotInvocation": return node.fallback.some(hasElementPortal);
97249
+ default: return false;
97250
+ }
97251
+ }
96062
97252
  function emitLit(ir, opts = {}) {
96063
97253
  const litImports = new LitImportCollector();
96064
97254
  const decoratorImports = new LitDecoratorImportCollector();
@@ -96075,11 +97265,13 @@ function emitLit(ir, opts = {}) {
96075
97265
  deconflictReservedComputedInjectNames(ir, reservedClassMembers("lit"), new Set([...(ir.expose ?? []).map((e) => e.name), ...ir.props.map((p) => p.name)]));
96076
97266
  const slotResult = emitSlotDecl(ir, { decorators: decoratorImports });
96077
97267
  diagnostics.push(...slotResult.diagnostics);
97268
+ const componentHasElementPortal = hasElementPortal(ir.template);
96078
97269
  const styleResult = emitStyle(ir.styles, opts.source ?? "", {
96079
97270
  componentName: ir.name,
96080
97271
  lit: litImports,
96081
97272
  runtime: runtimeImports,
96082
- scopeHash
97273
+ scopeHash,
97274
+ hasElementPortal: componentHasElementPortal
96083
97275
  });
96084
97276
  diagnostics.push(...styleResult.diagnostics);
96085
97277
  const scriptResult = emitScript(ir, {
@@ -96088,7 +97280,8 @@ function emitLit(ir, opts = {}) {
96088
97280
  runtime: runtimeImports,
96089
97281
  lit: litImports,
96090
97282
  context: contextImports,
96091
- portalScopeHash: scopeHash
97283
+ portalScopeHash: scopeHash,
97284
+ hasElementPortal: componentHasElementPortal
96092
97285
  });
96093
97286
  diagnostics.push(...scriptResult.diagnostics);
96094
97287
  const listenersResult = emitListeners(ir, {
@@ -96108,8 +97301,8 @@ function emitLit(ir, opts = {}) {
96108
97301
  if (templateResult.rozieSpreadUsed) runtimeImports.add("rozieSpread");
96109
97302
  if (templateResult.rozieListenersUsed) runtimeImports.add("rozieListeners");
96110
97303
  if (ir.adoptDocumentStyles) runtimeImports.add("adoptDocumentStyles");
96111
- const declaredAttrSkipNames = Array.from(new Set(ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()]))).filter((n) => n.length > 0);
96112
- const litAttrsGetter = templateResult.rozieSpreadUsed ? declaredAttrSkipNames.length > 0 ? [
97304
+ const declaredAttrSkipNames = Array.from(new Set(["data-rozie-ref", ...ir.props.flatMap((p) => [toKebabCase(p.name), p.name.toLowerCase()])])).filter((n) => n.length > 0);
97305
+ const litAttrsGetter = templateResult.rozieSpreadUsed ? [
96113
97306
  " /**",
96114
97307
  " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
96115
97308
  " * host custom element's attributes on each call so a consumer-side bound",
@@ -96121,6 +97314,12 @@ function emitLit(ir, opts = {}) {
96121
97314
  " * with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming",
96122
97315
  " * forms are folded into the skip set: kebab-case for model props",
96123
97316
  " * (explicit `attribute:`) AND lowercased property name (Lit's default).",
97317
+ " *",
97318
+ " * command-palette-per-level-virtual / portal-through-portal cluster —",
97319
+ " * `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping",
97320
+ " * attribute, never a consumer prop) so a parent-assigned `ref=` on this",
97321
+ " * component's own host tag can never clobber this component's OWN",
97322
+ " * internal `data-rozie-ref` ref markers via fallthrough re-application.",
96124
97323
  " */",
96125
97324
  " private get $attrs(): Record<string, string> {",
96126
97325
  ` const __skip = new Set<string>([${declaredAttrSkipNames.map((n) => `'${n}'`).join(", ")}]);`,
@@ -96131,18 +97330,6 @@ function emitLit(ir, opts = {}) {
96131
97330
  " }",
96132
97331
  " return out;",
96133
97332
  " }"
96134
- ].join("\n") : [
96135
- " /**",
96136
- " * Plan 14-05 — cross-framework attribute fallthrough source. Reads the",
96137
- " * host custom element's attributes on each call so a consumer-side bound",
96138
- " * attribute flows through on every render. The `rozieSpread` directive",
96139
- " * (D-02) does the cross-render diff downstream.",
96140
- " */",
96141
- " private get $attrs(): Record<string, string> {",
96142
- " const out: Record<string, string> = {};",
96143
- " for (const a of Array.from(this.attributes)) out[a.name] = a.value;",
96144
- " return out;",
96145
- " }"
96146
97333
  ].join("\n") : "";
96147
97334
  const litListenersGetter = templateResult.rozieListenersUsed ? [
96148
97335
  " /**",
@@ -96167,6 +97354,7 @@ function emitLit(ir, opts = {}) {
96167
97354
  debouncedFieldDecls: templateResult.debouncedFieldDecls.join("\n"),
96168
97355
  hoistedLiteralFieldDecls: templateResult.hoistedLiteralFieldDecls.join("\n"),
96169
97356
  keynavFieldDecls: templateResult.keynavFieldDecls.join("\n"),
97357
+ portalFieldDecls: templateResult.portalFieldDecls.join("\n"),
96170
97358
  slotFillerClassFields: templateResult.slotFillerClassFields.map((f) => " " + f).join("\n"),
96171
97359
  slotFields: slotResult.fields,
96172
97360
  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;",
@@ -96243,6 +97431,7 @@ function composeClassBody(parts) {
96243
97431
  if (parts.debouncedFieldDecls.trim().length > 0) sections.push(parts.debouncedFieldDecls);
96244
97432
  if (parts.hoistedLiteralFieldDecls.trim().length > 0) sections.push(parts.hoistedLiteralFieldDecls);
96245
97433
  if (parts.keynavFieldDecls.trim().length > 0) sections.push(parts.keynavFieldDecls);
97434
+ if (parts.portalFieldDecls.trim().length > 0) sections.push(parts.portalFieldDecls);
96246
97435
  if (parts.slotFillerClassFields.trim().length > 0) sections.push(parts.slotFillerClassFields);
96247
97436
  if (parts.slotFields.trim().length > 0) sections.push(parts.slotFields);
96248
97437
  sections.push(parts.cleanupField);
@@ -96326,7 +97515,8 @@ function buildComponentImportsBlock(components, selfName) {
96326
97515
  const lines = [];
96327
97516
  for (const decl of components) {
96328
97517
  if (decl.localName === selfName) continue;
96329
- lines.push(`import '${decl.importPath}';`);
97518
+ const importPath = isPublishedSpecifier(decl.importPath) ? rewriteRozieImport(decl.importPath, "lit") : decl.importPath;
97519
+ lines.push(`import '${importPath}';`);
96330
97520
  }
96331
97521
  if (lines.length === 0) return void 0;
96332
97522
  return lines.join("\n") + "\n";
@@ -96441,9 +97631,9 @@ function compile(source, opts) {
96441
97631
  });
96442
97632
  const acc = [...parseDiags, ...irDiags];
96443
97633
  if (!ir) return guardEmpty(fail(acc));
96444
- threadParamTypes(ir, filename ?? "<anonymous>", cache, resolver, acc);
97634
+ threadParamTypes(ir, filename ?? "<anonymous>", cache, resolver, opts.target, acc);
96445
97635
  validatePortalScopedStyle(ir, acc);
96446
- validateTwoWayBindings(ir, filename ?? "<anonymous>", cache, resolver, acc);
97636
+ validateTwoWayBindings(ir, filename ?? "<anonymous>", cache, resolver, opts.target, acc);
96447
97637
  stampMissingFilename(acc, filename);
96448
97638
  if (acc.some((d) => d.severity === "error")) return fail(acc);
96449
97639
  const emitOpts = {