@sola-air-ui/compiler 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sola-air-ui/compiler",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Compiler for Sola single-file components (.sola) \u2014 transforms template + script + style to ES modules",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/cli.js CHANGED
@@ -18,7 +18,7 @@ if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
18
18
  --target <esm|iife> Output format (default: esm)
19
19
  esm — ES module with import/export (default, for bundlers)
20
20
  iife — Self-contained IIFE for no-bundler environments
21
- (ServiceNow Service Portal, plain <script> tags)
21
+ (enterprise portal widgets, plain <script> tags)
22
22
  --export-name <Name> Global name for IIFE target (default: SolaComponent)
23
23
  The compiled component mounts as window['Name'](el, props)
24
24
  -v, --version Print compiler version
package/src/index.js CHANGED
@@ -6,6 +6,14 @@ const { version: COMPILER_VERSION } = JSON.parse(
6
6
  readFileSync(new URL('../package.json', import.meta.url), 'utf8')
7
7
  );
8
8
 
9
+ // Names every compiled component imports from core under their own name. A
10
+ // script that imports one of these again is not given a second binding.
11
+ const CORE_PROVIDED = new Set([
12
+ 'createSignal', 'createDerived', 'createEffect', 'createIntent', 'createData',
13
+ 'createPersistentSignal', 'onMount', 'onDestroy', 'pushContext', 'popContext',
14
+ '__flush_mounts', '__flush_destroys', '__untrack'
15
+ ]);
16
+
9
17
  const VOID_ELEMENTS = new Set([
10
18
  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
11
19
  'link', 'meta', 'param', 'source', 'track', 'wbr'
@@ -325,7 +333,7 @@ function preprocessTemplate(template, dynAttrs, handlers) {
325
333
  } else if (hasDyn) {
326
334
  // Build a string-concatenation expression instead of a runtime backtick
327
335
  // template literal. Confirmed by driving a real browser against a live
328
- // ServiceNow Service Portal instance: its widget-script delivery pipeline
336
+ // enterprise portal instance: its widget-script delivery pipeline
329
337
  // silently mangles `${expr}` interpolation inside a template literal down
330
338
  // to bare, unevaluated expression text (e.g. `background: ${x}` becomes
331
339
  // the literal string "background: x" — the function reference itself,
@@ -889,6 +897,12 @@ export function compile(source, options = {}) {
889
897
  const stateVars = new Set();
890
898
  const exportedProps = [];
891
899
  const componentImports = [];
900
+ // Every other import in the script. They used to be deleted, so a package
901
+ // import (`import DataCard from '@sola-air-ui/ui/data-card'`, or anything from
902
+ // '@sola-air-ui/core' that the compiler does not already provide) was gone from
903
+ // the output and left an undefined name behind.
904
+ const otherImports = [];
905
+ const coreImportExtras = [];
892
906
 
893
907
  let reactiveVars = NO_REACTIVE_VARS;
894
908
  if (strippedScript.trim()) {
@@ -914,6 +928,21 @@ export function compile(source, options = {}) {
914
928
  node.specifiers.forEach(spec => {
915
929
  componentImports.push({ localName: spec.local.name, path: src });
916
930
  });
931
+ } else if (src === '@sola-air-ui/core' || src === '@sola/core') {
932
+ // The compiler already imports these names under their own names.
933
+ // Importing one again would be a duplicate binding, so keep only the
934
+ // rest: names it does not provide, and aliased ones.
935
+ node.specifiers.forEach(spec => {
936
+ if (spec.type !== 'ImportSpecifier') return;
937
+ const imported = spec.imported.name ?? spec.imported.value;
938
+ if (spec.local.name === imported && CORE_PROVIDED.has(imported)) return;
939
+ coreImportExtras.push({ imported, local: spec.local.name });
940
+ });
941
+ } else {
942
+ otherImports.push({
943
+ code: strippedScript.slice(node.start, node.end).trim().replace(/;?$/, ';'),
944
+ locals: node.specifiers.map(spec => spec.local.name)
945
+ });
917
946
  }
918
947
  edits.push({
919
948
  start: node.start,
@@ -947,6 +976,14 @@ export function compile(source, options = {}) {
947
976
  edits.push({ start: call.end, end: call.end, replacement: ')' });
948
977
  }
949
978
 
979
+ // $effect(fn) as a bare statement: the form the docs show. It compiles to
980
+ // createEffect(fn); the declaration form (const stop = $effect(fn)) is below.
981
+ if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' &&
982
+ node.expression.callee.type === 'Identifier' && node.expression.callee.name === '$effect') {
983
+ const callee = node.expression.callee;
984
+ edits.push({ start: callee.start, end: callee.end, replacement: 'createEffect' });
985
+ }
986
+
950
987
  // $state()/$derived()/$effect()/$intent()/$data() macro declarations.
951
988
  // Bracket edits (head + tail) leave the argument expression in place so
952
989
  // the reactive rewrite pass below can still auto-call signals inside it.
@@ -1050,6 +1087,11 @@ export function compile(source, options = {}) {
1050
1087
  let uid = 0;
1051
1088
  let domCode = '';
1052
1089
  const importedComponentSet = new Set(componentImports.map(i => i.localName));
1090
+ // A capitalised tag that matches a name imported from a package is a component
1091
+ // too (`import DataCard from '@sola-air-ui/ui/data-card'`), not only a `.sola` path.
1092
+ for (const imp of otherImports) {
1093
+ for (const name of imp.locals) if (/^[A-Z]/.test(name)) importedComponentSet.add(name);
1094
+ }
1053
1095
 
1054
1096
  const sourceFile = options.filename || '<component>';
1055
1097
 
@@ -1130,25 +1172,43 @@ export function compile(source, options = {}) {
1130
1172
  domCode += ` ${parentVar}.appendChild(${cid}_target);\n`;
1131
1173
 
1132
1174
  // Dynamically-bound props (rows={items}) arrive here as __soladyn_N__ placeholders —
1133
- // resolve them back to the real expression from dynAttrs and pass it as a raw
1134
- // (unquoted) property so the child receives live data, not the placeholder text.
1175
+ // resolve them back to the real expression from dynAttrs.
1135
1176
  //
1136
1177
  // `onXxx`-named props (e.g. onChange={handler}) arrive as a
1137
1178
  // __solahandler_N__ marker, because preprocessTemplate cannot tell a
1138
1179
  // component apart from a native element and lifts both the same way.
1139
1180
  // Either way the expression is passed through as a live function.
1181
+ //
1182
+ // Three kinds of dynamic prop:
1183
+ // - a bare name (rows={items}) is passed as-is. For a $state that is the
1184
+ // signal accessor, which is how a child receives live data (the shipped
1185
+ // components accept either an accessor or a plain value);
1186
+ // - an expression that reads a signal (title={ctx.title}) is evaluated
1187
+ // with the signal reads rewritten, and the child is re-mounted when
1188
+ // the value changes;
1189
+ // - an expression that reads no signal (text={msg.text} in an {#each})
1190
+ // is evaluated once, when the child is mounted.
1140
1191
  const propEntries = [];
1192
+ let reactiveProps = false;
1141
1193
  for (const [key, val] of Object.entries(node.attribs || {})) {
1142
1194
  const dynMatch = /^__soladyn_(\d+)__$/.exec(val);
1143
1195
  const handlerMatch = /^__solahandler_(\d+)__$/.exec(val);
1144
- // Dynamic (non-event) component props are deliberately NOT auto-called:
1145
- // passing the getter is how a child receives live data today, and
1146
- // README-syntax components rely on it.
1147
- const propValue = dynMatch
1148
- ? `(${dynAttrs[parseInt(dynMatch[1], 10)]})`
1149
- : handlerMatch
1150
- ? `(${rewrite(handlers[parseInt(handlerMatch[1], 10)])})`
1151
- : JSON.stringify(val);
1196
+ let propValue;
1197
+ if (dynMatch) {
1198
+ const raw = dynAttrs[parseInt(dynMatch[1], 10)];
1199
+ if (/^\s*[A-Za-z_$][\w$]*\s*$/.test(raw)) {
1200
+ propValue = `(${raw})`;
1201
+ } else {
1202
+ checkExpression(raw, 'component prop');
1203
+ const rewritten = rewrite(raw);
1204
+ if (rewritten !== raw) reactiveProps = true;
1205
+ propValue = `(${rewritten})`;
1206
+ }
1207
+ } else if (handlerMatch) {
1208
+ propValue = `(${rewrite(handlers[parseInt(handlerMatch[1], 10)])})`;
1209
+ } else {
1210
+ propValue = JSON.stringify(val);
1211
+ }
1152
1212
  propEntries.push(`${JSON.stringify(key)}: ${propValue}`);
1153
1213
  }
1154
1214
 
@@ -1162,7 +1222,21 @@ export function compile(source, options = {}) {
1162
1222
  propEntries.push(`children: ${slotVar}`);
1163
1223
  }
1164
1224
 
1165
- domCode += ` ${node.name}(${cid}_target, { ${propEntries.join(', ')} });\n`;
1225
+ // The child mounts untracked, so nothing it reads while building itself
1226
+ // can re-run the parent's effect. Its teardown is kept and registered with
1227
+ // whatever scope is being built, so it runs when that scope is destroyed.
1228
+ domCode += ` let ${cid}_unmount = null;\n`;
1229
+ domCode += ` onDestroy(() => { if (typeof ${cid}_unmount === 'function') ${cid}_unmount(); ${cid}_unmount = null; });\n`;
1230
+ if (reactiveProps) {
1231
+ domCode += ` createEffect(() => {\n`;
1232
+ domCode += ` const ${cid}_props = { ${propEntries.join(', ')} };\n`;
1233
+ domCode += ` if (typeof ${cid}_unmount === 'function') __untrack(${cid}_unmount);\n`;
1234
+ domCode += ` ${cid}_target.textContent = '';\n`;
1235
+ domCode += ` ${cid}_unmount = __untrack(() => ${node.name}(${cid}_target, ${cid}_props));\n`;
1236
+ domCode += ` });\n`;
1237
+ } else {
1238
+ domCode += ` ${cid}_unmount = __untrack(() => ${node.name}(${cid}_target, { ${propEntries.join(', ')} }));\n`;
1239
+ }
1166
1240
  return;
1167
1241
  }
1168
1242
 
@@ -1173,37 +1247,50 @@ export function compile(source, options = {}) {
1173
1247
  }
1174
1248
 
1175
1249
  // {#if ...} {:else} ... {/if}
1250
+ //
1251
+ // Each time a branch is built it gets its own scope (pushContext). Effects,
1252
+ // onDestroy callbacks and child components created inside register with that
1253
+ // scope, and switching or removing the branch destroys it, so nothing built
1254
+ // for a branch that is no longer on the page keeps running.
1176
1255
  if (node.name === 'sola-if') {
1177
1256
  const cid = `c${uid++}`;
1178
1257
  domCode += ` const ${cid}_a = document.createComment('if');\n`;
1179
1258
  domCode += ` ${parentVar}.appendChild(${cid}_a);\n`;
1180
1259
  domCode += ` let ${cid}_els = [];\n`;
1181
-
1260
+ domCode += ` let ${cid}_ctx = null;\n`;
1261
+ domCode += ` onDestroy(() => { if (${cid}_ctx) { __flush_destroys(${cid}_ctx); ${cid}_ctx = null; } });\n`;
1262
+
1182
1263
  const thenChild = node.children.find(c => c.name === 'sola-then');
1183
1264
  const elseChild = node.children.find(c => c.name === 'sola-else');
1184
1265
 
1185
- domCode += ` createEffect(() => {\n`;
1186
- domCode += ` ${cid}_els.forEach(e => e.remove()); ${cid}_els = [];\n`;
1187
- domCode += ` if (${rewrite(node.attribs.condition)}) {\n`;
1188
- if (thenChild) {
1266
+ const emitBranch = (branch) => {
1267
+ if (!branch) return;
1268
+ domCode += ` ${cid}_ctx = pushContext();\n`;
1269
+ domCode += ` try {\n`;
1189
1270
  domCode += ` const f = document.createDocumentFragment();\n`;
1190
- thenChild.children.forEach(child => emitNode(child, 'f', locals));
1271
+ branch.children.forEach(child => emitNode(child, 'f', locals));
1191
1272
  domCode += ` Array.from(f.childNodes).forEach(n => ${cid}_els.push(n));\n`;
1192
1273
  domCode += ` ${cid}_a.parentNode.insertBefore(f, ${cid}_a.nextSibling);\n`;
1193
- }
1274
+ domCode += ` } finally { popContext(${cid}_ctx); }\n`;
1275
+ };
1276
+
1277
+ domCode += ` createEffect(() => {\n`;
1278
+ domCode += ` if (${cid}_ctx) { __flush_destroys(${cid}_ctx); ${cid}_ctx = null; }\n`;
1279
+ domCode += ` ${cid}_els.forEach(e => e.remove()); ${cid}_els = [];\n`;
1280
+ domCode += ` if (${rewrite(node.attribs.condition)}) {\n`;
1281
+ emitBranch(thenChild);
1194
1282
  domCode += ` } else {\n`;
1195
- if (elseChild) {
1196
- domCode += ` const f = document.createDocumentFragment();\n`;
1197
- elseChild.children.forEach(child => emitNode(child, 'f', locals));
1198
- domCode += ` Array.from(f.childNodes).forEach(n => ${cid}_els.push(n));\n`;
1199
- domCode += ` ${cid}_a.parentNode.insertBefore(f, ${cid}_a.nextSibling);\n`;
1200
- }
1283
+ emitBranch(elseChild);
1201
1284
  domCode += ` }\n`;
1202
1285
  domCode += ` });\n`;
1203
1286
  return;
1204
1287
  }
1205
1288
 
1206
1289
  // {#each} with optional keyed diffing: {#each items as item (item.id)}
1290
+ //
1291
+ // Like {#if}, what is built for the list gets its own scope so it can be
1292
+ // destroyed: the whole list for an unkeyed each, one scope per key for a
1293
+ // keyed one (so removing an item stops that item's effects and children).
1207
1294
  if (node.name === 'sola-each') {
1208
1295
  const eid = `e${uid++}`;
1209
1296
  // Item/index names shadow signals inside the loop body and key expression
@@ -1217,70 +1304,53 @@ export function compile(source, options = {}) {
1217
1304
  domCode += ` const ${eid}_a = document.createComment('each');\n`;
1218
1305
  domCode += ` ${parentVar}.appendChild(${eid}_a);\n`;
1219
1306
  if (keyExpr) {
1220
- // Keyed: maintain a Map<key, Node[]> and reconcile on each run
1307
+ // Keyed: maintain a Map<key, { nodes, ctx }> and reconcile on each run
1221
1308
  // Reconcile by walking the new order and moving each key's nodes into
1222
1309
  // place against a cursor. The previous version only ever inserted, and
1223
1310
  // against an anchor it never advanced — so reordering a keyed list (a
1224
1311
  // sort, a drag, a reverse) left the DOM in its original order, silently.
1225
- domCode += ` let ${eid}_keyMap = new Map();
1226
- `;
1227
- domCode += ` createEffect(() => {
1228
- `;
1229
- domCode += ` const _items = ${arrayExpr} || [];
1230
- `;
1231
- domCode += ` const _parent = ${eid}_a.parentNode;
1232
- `;
1233
- domCode += ` const _seen = new Set();
1234
- `;
1235
- domCode += ` let _cursor = ${eid}_a.nextSibling;
1236
- `;
1237
- domCode += ` for (let _i = 0; _i < _items.length; _i++) {
1238
- `;
1239
- domCode += ` const ${node.attribs.item} = _items[_i];
1240
- `;
1241
- if (node.attribs.index) domCode += ` const ${node.attribs.index} = _i;
1242
- `;
1243
- domCode += ` const _key = String(${keyExpr});
1244
- `;
1245
- domCode += ` _seen.add(_key);
1246
- `;
1247
- domCode += ` let _nodes = ${eid}_keyMap.get(_key);
1248
- `;
1249
- domCode += ` if (!_nodes) {
1250
- `;
1251
- domCode += ` const f = document.createDocumentFragment();
1252
- `;
1312
+ domCode += ` let ${eid}_keyMap = new Map();\n`;
1313
+ domCode += ` onDestroy(() => { for (const _e of ${eid}_keyMap.values()) __flush_destroys(_e.ctx); ${eid}_keyMap.clear(); });\n`;
1314
+ domCode += ` createEffect(() => {\n`;
1315
+ domCode += ` const _items = ${arrayExpr} || [];\n`;
1316
+ domCode += ` const _parent = ${eid}_a.parentNode;\n`;
1317
+ domCode += ` const _seen = new Set();\n`;
1318
+ domCode += ` let _cursor = ${eid}_a.nextSibling;\n`;
1319
+ domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
1320
+ domCode += ` const ${node.attribs.item} = _items[_i];\n`;
1321
+ if (node.attribs.index) domCode += ` const ${node.attribs.index} = _i;\n`;
1322
+ domCode += ` const _key = String(${keyExpr});\n`;
1323
+ domCode += ` _seen.add(_key);\n`;
1324
+ domCode += ` let _entry = ${eid}_keyMap.get(_key);\n`;
1325
+ domCode += ` if (!_entry) {\n`;
1326
+ domCode += ` const _kctx = pushContext();\n`;
1327
+ domCode += ` try {\n`;
1328
+ domCode += ` const f = document.createDocumentFragment();\n`;
1253
1329
  node.children.forEach(child => emitNode(child, 'f', eachLocals));
1254
- domCode += ` _nodes = Array.from(f.childNodes);
1255
- `;
1256
- domCode += ` ${eid}_keyMap.set(_key, _nodes);
1257
- `;
1258
- domCode += ` }
1259
- `;
1260
- domCode += ` for (const _n of _nodes) {
1261
- `;
1262
- domCode += ` if (_n === _cursor) { _cursor = _cursor.nextSibling; }
1263
- `;
1264
- domCode += ` else { _parent.insertBefore(_n, _cursor); }
1265
- `;
1266
- domCode += ` }
1267
- `;
1268
- domCode += ` }
1269
- `;
1270
- domCode += ` for (const [_k, _stale] of ${eid}_keyMap) {
1271
- `;
1272
- domCode += ` if (!_seen.has(_k)) { _stale.forEach(n => n.remove()); ${eid}_keyMap.delete(_k); }
1273
- `;
1274
- domCode += ` }
1275
- `;
1276
- domCode += ` });
1277
- `;
1330
+ domCode += ` _entry = { nodes: Array.from(f.childNodes), ctx: _kctx };\n`;
1331
+ domCode += ` } finally { popContext(_kctx); }\n`;
1332
+ domCode += ` ${eid}_keyMap.set(_key, _entry);\n`;
1333
+ domCode += ` }\n`;
1334
+ domCode += ` for (const _n of _entry.nodes) {\n`;
1335
+ domCode += ` if (_n === _cursor) { _cursor = _cursor.nextSibling; }\n`;
1336
+ domCode += ` else { _parent.insertBefore(_n, _cursor); }\n`;
1337
+ domCode += ` }\n`;
1338
+ domCode += ` }\n`;
1339
+ domCode += ` for (const [_k, _stale] of ${eid}_keyMap) {\n`;
1340
+ domCode += ` if (!_seen.has(_k)) { __flush_destroys(_stale.ctx); _stale.nodes.forEach(n => n.remove()); ${eid}_keyMap.delete(_k); }\n`;
1341
+ domCode += ` }\n`;
1342
+ domCode += ` });\n`;
1278
1343
  } else {
1279
1344
  // Unkeyed: full teardown + rebuild
1280
1345
  domCode += ` let ${eid}_els = [];\n`;
1346
+ domCode += ` let ${eid}_ctx = null;\n`;
1347
+ domCode += ` onDestroy(() => { if (${eid}_ctx) { __flush_destroys(${eid}_ctx); ${eid}_ctx = null; } });\n`;
1281
1348
  domCode += ` createEffect(() => {\n`;
1349
+ domCode += ` if (${eid}_ctx) { __flush_destroys(${eid}_ctx); ${eid}_ctx = null; }\n`;
1282
1350
  domCode += ` ${eid}_els.forEach(e => e.remove()); ${eid}_els = [];\n`;
1283
1351
  domCode += ` const _items = ${arrayExpr} || [];\n`;
1352
+ domCode += ` ${eid}_ctx = pushContext();\n`;
1353
+ domCode += ` try {\n`;
1284
1354
  domCode += ` const f = document.createDocumentFragment();\n`;
1285
1355
  domCode += ` for (let _i = 0; _i < _items.length; _i++) {\n`;
1286
1356
  domCode += ` const ${node.attribs.item} = _items[_i];\n`;
@@ -1289,6 +1359,7 @@ export function compile(source, options = {}) {
1289
1359
  domCode += ` }\n`;
1290
1360
  domCode += ` Array.from(f.childNodes).forEach(n => ${eid}_els.push(n));\n`;
1291
1361
  domCode += ` ${eid}_a.parentNode.insertBefore(f, ${eid}_a.nextSibling);\n`;
1362
+ domCode += ` } finally { popContext(${eid}_ctx); }\n`;
1292
1363
  domCode += ` });\n`;
1293
1364
  }
1294
1365
  return;
@@ -1356,7 +1427,7 @@ export function compile(source, options = {}) {
1356
1427
  root.children.forEach(child => emitNode(child, '__target'));
1357
1428
 
1358
1429
  // Assemble final output module
1359
- const CORE_EXPORTS = 'createSignal, createDerived, createEffect, createIntent, createData, createPersistentSignal, onMount, onDestroy, pushContext, popContext, __flush_mounts, __flush_destroys';
1430
+ const CORE_EXPORTS = [...CORE_PROVIDED].join(', ');
1360
1431
  // The default bus is imported under an alias so a user variable named `bus`
1361
1432
  // cannot shadow what $on / $lastEvent compile to.
1362
1433
  const ESM_CORE_IMPORTS = CORE_EXPORTS + ', bus as __solaBus';
@@ -1365,19 +1436,30 @@ export function compile(source, options = {}) {
1365
1436
  output += `// Compiled by @sola-air-ui/compiler v${COMPILER_VERSION}\n`;
1366
1437
 
1367
1438
  if (target === 'iife') {
1368
- // ServiceNow / no-bundler mode: pull from window.SolaCore global
1439
+ // No-bundler mode: pull from window.SolaCore global
1369
1440
  output += `(function() {\n`;
1370
- output += `const { ${IIFE_CORE_PULL} } = window.SolaCore;\n`;
1441
+ const iifeExtras = coreImportExtras.map(e => (e.local === e.imported ? e.imported : `${e.imported}: ${e.local}`));
1442
+ output += `const { ${[IIFE_CORE_PULL, ...iifeExtras].join(', ')} } = window.SolaCore;\n`;
1371
1443
  for (const imp of componentImports) {
1372
1444
  output += `// Note: sub-component ${imp.localName} must be pre-loaded before this script\n`;
1373
1445
  }
1446
+ for (const imp of otherImports) {
1447
+ output += `// Note: ${imp.locals.join(', ')} must be loaded before this script (no-bundler output cannot import)\n`;
1448
+ }
1374
1449
  output += `\n`;
1375
1450
  output += `window['${options.exportName || 'SolaComponent'}'] = function mount(__target, props = {}) {\n`;
1376
1451
  } else {
1377
1452
  output += `import { ${ESM_CORE_IMPORTS} } from '@sola-air-ui/core';\n`;
1453
+ if (coreImportExtras.length) {
1454
+ const names = coreImportExtras.map(e => (e.local === e.imported ? e.imported : `${e.imported} as ${e.local}`));
1455
+ output += `import { ${names.join(', ')} } from '@sola-air-ui/core';\n`;
1456
+ }
1378
1457
  for (const imp of componentImports) {
1379
1458
  output += `import ${imp.localName} from '${imp.path}';\n`;
1380
1459
  }
1460
+ for (const imp of otherImports) {
1461
+ output += `${imp.code}\n`;
1462
+ }
1381
1463
  output += `\n`;
1382
1464
  output += `export default function mount(__target, props = {}) {\n`;
1383
1465
  }
@@ -1462,6 +1544,9 @@ function inferPropType(defaultSrc) {
1462
1544
  if (/^(['"`])[\s\S]*\1$/.test(d)) return 'string';
1463
1545
  if (/^-?\d[\d_]*(\.\d+)?([eE][+-]?\d+)?$/.test(d)) return 'number';
1464
1546
  if (d === 'true' || d === 'false') return 'boolean';
1547
+ // A null or undefined default says nothing about the type. `unknown` would stop a
1548
+ // TypeScript host from reading or calling the prop; `any` lets it.
1549
+ if (d === 'null' || d === 'undefined') return 'any';
1465
1550
  if (d.startsWith('[')) return 'unknown[]';
1466
1551
  if (d.startsWith('{')) return 'Record<string, unknown>';
1467
1552
  if (/^(\(.*\)|\w+)\s*=>|^function\b/.test(d)) return '(...args: any[]) => any';
package/types/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export interface CompileOptions {
4
4
  /**
5
5
  * 'esm' (default) emits a module importing from @sola-air-ui/core.
6
6
  * 'iife' emits a self-contained bundle reading from window.SolaCore, for
7
- * ServiceNow and other no-bundler hosts.
7
+ * enterprise portals and other no-bundler hosts.
8
8
  */
9
9
  target?: 'esm' | 'iife';
10
10
  /** Global name the IIFE build registers itself under. Default 'SolaComponent'. */