@alloy-js/core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/babel.config.cjs +4 -1
  3. package/dist/src/binder.d.ts +20 -13
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +33 -15
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/code.d.ts +2 -2
  8. package/dist/src/code.d.ts.map +1 -1
  9. package/dist/src/code.js +4 -4
  10. package/dist/src/code.js.map +1 -1
  11. package/dist/src/components/Block.d.ts +25 -0
  12. package/dist/src/components/Block.d.ts.map +1 -0
  13. package/dist/src/components/Block.js +25 -0
  14. package/dist/src/components/Block.js.map +1 -0
  15. package/dist/src/components/Declaration.d.ts +1 -1
  16. package/dist/src/components/Declaration.d.ts.map +1 -1
  17. package/dist/src/components/Declaration.js +4 -0
  18. package/dist/src/components/Declaration.js.map +1 -1
  19. package/dist/src/components/For.d.ts +44 -0
  20. package/dist/src/components/For.d.ts.map +1 -0
  21. package/dist/src/components/For.js +41 -0
  22. package/dist/src/components/For.js.map +1 -0
  23. package/dist/src/components/Indent.d.ts +6 -10
  24. package/dist/src/components/Indent.d.ts.map +1 -1
  25. package/dist/src/components/Indent.js +7 -18
  26. package/dist/src/components/Indent.js.map +1 -1
  27. package/dist/src/components/List.d.ts +38 -0
  28. package/dist/src/components/List.d.ts.map +1 -0
  29. package/dist/src/components/List.js +40 -0
  30. package/dist/src/components/List.js.map +1 -0
  31. package/dist/src/components/MemberDeclaration.d.ts +1 -1
  32. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  33. package/dist/src/components/MemberDeclaration.js.map +1 -1
  34. package/dist/src/components/MemberName.d.ts +1 -1
  35. package/dist/src/components/MemberName.d.ts.map +1 -1
  36. package/dist/src/components/MemberName.js +1 -1
  37. package/dist/src/components/MemberName.js.map +1 -1
  38. package/dist/src/components/MemberScope.d.ts +1 -1
  39. package/dist/src/components/MemberScope.d.ts.map +1 -1
  40. package/dist/src/components/MemberScope.js.map +1 -1
  41. package/dist/src/components/Name.d.ts +1 -1
  42. package/dist/src/components/Name.d.ts.map +1 -1
  43. package/dist/src/components/Name.js +1 -1
  44. package/dist/src/components/Name.js.map +1 -1
  45. package/dist/src/components/Output.d.ts +3 -2
  46. package/dist/src/components/Output.d.ts.map +1 -1
  47. package/dist/src/components/Output.js +12 -2
  48. package/dist/src/components/Output.js.map +1 -1
  49. package/dist/src/components/Scope.d.ts +1 -1
  50. package/dist/src/components/Scope.d.ts.map +1 -1
  51. package/dist/src/components/Scope.js.map +1 -1
  52. package/dist/src/components/Show.d.ts +8 -0
  53. package/dist/src/components/Show.d.ts.map +1 -0
  54. package/dist/src/components/Show.js +4 -0
  55. package/dist/src/components/Show.js.map +1 -0
  56. package/dist/src/components/SourceDirectory.d.ts +2 -2
  57. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  58. package/dist/src/components/SourceDirectory.js +1 -0
  59. package/dist/src/components/SourceDirectory.js.map +1 -1
  60. package/dist/src/components/SourceFile.d.ts +4 -8
  61. package/dist/src/components/SourceFile.d.ts.map +1 -1
  62. package/dist/src/components/SourceFile.js +6 -13
  63. package/dist/src/components/SourceFile.js.map +1 -1
  64. package/dist/src/components/StatementList.d.ts +9 -0
  65. package/dist/src/components/StatementList.d.ts.map +1 -0
  66. package/dist/src/components/StatementList.js +17 -0
  67. package/dist/src/components/StatementList.js.map +1 -0
  68. package/dist/src/components/Switch.d.ts +41 -0
  69. package/dist/src/components/Switch.d.ts.map +1 -0
  70. package/dist/src/components/Switch.js +41 -0
  71. package/dist/src/components/Switch.js.map +1 -0
  72. package/dist/src/components/Wrap.d.ts +20 -0
  73. package/dist/src/components/Wrap.d.ts.map +1 -0
  74. package/dist/src/components/Wrap.js +15 -0
  75. package/dist/src/components/Wrap.js.map +1 -0
  76. package/dist/src/components/index.d.ts +8 -1
  77. package/dist/src/components/index.d.ts.map +1 -1
  78. package/dist/src/components/index.js +7 -0
  79. package/dist/src/components/index.js.map +1 -1
  80. package/dist/src/components/stc/index.d.ts +89 -18
  81. package/dist/src/components/stc/index.d.ts.map +1 -1
  82. package/dist/src/components/stc/index.js +17 -1
  83. package/dist/src/components/stc/index.js.map +1 -1
  84. package/dist/src/context/index.d.ts +0 -1
  85. package/dist/src/context/index.d.ts.map +1 -1
  86. package/dist/src/context/index.js +0 -1
  87. package/dist/src/context/index.js.map +1 -1
  88. package/dist/src/context/member-declaration.d.ts +1 -0
  89. package/dist/src/context/member-declaration.d.ts.map +1 -1
  90. package/dist/src/context/member-declaration.js +4 -1
  91. package/dist/src/context/member-declaration.js.map +1 -1
  92. package/dist/src/context.d.ts.map +1 -1
  93. package/dist/src/context.js +3 -3
  94. package/dist/src/context.js.map +1 -1
  95. package/dist/src/index.browser.d.ts +3 -0
  96. package/dist/src/index.browser.d.ts.map +1 -0
  97. package/dist/src/index.browser.js +3 -0
  98. package/dist/src/index.browser.js.map +1 -0
  99. package/dist/src/index.d.ts +2 -0
  100. package/dist/src/index.d.ts.map +1 -1
  101. package/dist/src/index.js +2 -0
  102. package/dist/src/index.js.map +1 -1
  103. package/dist/src/jsx-runtime.d.ts +151 -7
  104. package/dist/src/jsx-runtime.d.ts.map +1 -1
  105. package/dist/src/jsx-runtime.js +113 -12
  106. package/dist/src/jsx-runtime.js.map +1 -1
  107. package/dist/src/render.d.ts +107 -132
  108. package/dist/src/render.d.ts.map +1 -1
  109. package/dist/src/render.js +272 -178
  110. package/dist/src/render.js.map +1 -1
  111. package/dist/src/stc.d.ts +14 -0
  112. package/dist/src/stc.d.ts.map +1 -0
  113. package/dist/src/stc.js +52 -0
  114. package/dist/src/stc.js.map +1 -0
  115. package/dist/src/tap.d.ts +19 -0
  116. package/dist/src/tap.d.ts.map +1 -0
  117. package/dist/src/tap.js +39 -0
  118. package/dist/src/tap.js.map +1 -0
  119. package/dist/src/utils.d.ts +22 -15
  120. package/dist/src/utils.d.ts.map +1 -1
  121. package/dist/src/utils.js +95 -59
  122. package/dist/src/utils.js.map +1 -1
  123. package/dist/test/browser-build.test.d.ts +2 -0
  124. package/dist/test/browser-build.test.d.ts.map +1 -0
  125. package/dist/test/components/block.test.d.ts +2 -0
  126. package/dist/test/components/block.test.d.ts.map +1 -0
  127. package/dist/test/components/declaration.test.d.ts +2 -0
  128. package/dist/test/components/declaration.test.d.ts.map +1 -0
  129. package/dist/test/components/list.test.d.ts +2 -0
  130. package/dist/test/components/list.test.d.ts.map +1 -0
  131. package/dist/test/components/wrap.test.d.ts +2 -0
  132. package/dist/test/components/wrap.test.d.ts.map +1 -0
  133. package/dist/test/control-flow/for.test.d.ts +2 -0
  134. package/dist/test/control-flow/for.test.d.ts.map +1 -0
  135. package/dist/test/control-flow/match.test.d.ts +2 -0
  136. package/dist/test/control-flow/match.test.d.ts.map +1 -0
  137. package/dist/test/control-flow/show.test.d.ts +2 -0
  138. package/dist/test/control-flow/show.test.d.ts.map +1 -0
  139. package/dist/test/reactivity/cleanup.test.d.ts +2 -0
  140. package/dist/test/reactivity/cleanup.test.d.ts.map +1 -0
  141. package/dist/test/reactivity/memo.test.d.ts +2 -0
  142. package/dist/test/reactivity/memo.test.d.ts.map +1 -0
  143. package/dist/test/reactivity/untrack.test.d.ts +2 -0
  144. package/dist/test/reactivity/untrack.test.d.ts.map +1 -0
  145. package/dist/test/rendering/formatting.test.d.ts +2 -0
  146. package/dist/test/rendering/formatting.test.d.ts.map +1 -0
  147. package/dist/test/rendering/memoization.test.d.ts +2 -0
  148. package/dist/test/rendering/memoization.test.d.ts.map +1 -0
  149. package/dist/test/split-props.test.d.ts +2 -0
  150. package/dist/test/split-props.test.d.ts.map +1 -0
  151. package/dist/test/stc.test.d.ts.map +1 -1
  152. package/dist/test/utils.test.d.ts.map +1 -1
  153. package/dist/testing/extend-expect.js +4 -4
  154. package/dist/testing/extend-expect.js.map +1 -1
  155. package/dist/testing/render.d.ts +2 -3
  156. package/dist/testing/render.d.ts.map +1 -1
  157. package/dist/testing/render.js +2 -4
  158. package/dist/testing/render.js.map +1 -1
  159. package/dist/tsconfig.tsbuildinfo +1 -1
  160. package/package.json +8 -9
  161. package/src/binder.ts +60 -50
  162. package/src/code.ts +17 -12
  163. package/src/components/Block.tsx +44 -0
  164. package/src/components/Declaration.tsx +10 -4
  165. package/src/components/For.tsx +81 -0
  166. package/src/components/Indent.tsx +20 -27
  167. package/src/components/List.tsx +94 -0
  168. package/src/components/MemberDeclaration.tsx +9 -6
  169. package/src/components/MemberScope.tsx +4 -2
  170. package/src/components/Output.tsx +27 -14
  171. package/src/components/Scope.tsx +4 -2
  172. package/src/components/Show.tsx +11 -0
  173. package/src/components/SourceDirectory.tsx +6 -2
  174. package/src/components/SourceFile.tsx +13 -17
  175. package/src/components/StatementList.tsx +16 -0
  176. package/src/components/Switch.tsx +62 -0
  177. package/src/components/Wrap.tsx +29 -0
  178. package/src/components/index.tsx +8 -1
  179. package/src/components/stc/index.ts +18 -1
  180. package/src/context/index.ts +0 -1
  181. package/src/context/member-declaration.ts +9 -1
  182. package/src/context.ts +2 -3
  183. package/src/index.browser.ts +2 -0
  184. package/src/index.ts +2 -0
  185. package/src/jsx-runtime.ts +265 -23
  186. package/src/render.ts +382 -200
  187. package/src/stc.ts +95 -0
  188. package/src/tap.ts +69 -0
  189. package/src/utils.ts +162 -95
  190. package/temp/api.json +8042 -1886
  191. package/test/browser-build.test.ts +91 -0
  192. package/test/children.test.tsx +8 -10
  193. package/test/components/block.test.tsx +48 -0
  194. package/test/components/declaration.test.tsx +37 -0
  195. package/test/components/list.test.tsx +91 -0
  196. package/test/components/slot.test.tsx +31 -25
  197. package/test/components/source-file.test.tsx +11 -31
  198. package/test/components/wrap.test.tsx +42 -0
  199. package/test/control-flow/for.test.tsx +194 -0
  200. package/test/control-flow/match.test.tsx +49 -0
  201. package/test/control-flow/show.test.tsx +25 -0
  202. package/test/name-policy.test.tsx +5 -5
  203. package/test/reactivity/cleanup.test.tsx +91 -0
  204. package/test/reactivity/memo.test.tsx +17 -0
  205. package/test/reactivity/ref-rendering.test.tsx +3 -8
  206. package/test/reactivity/test.test.tsx +7 -6
  207. package/test/reactivity/untrack.test.ts +33 -0
  208. package/test/rendering/basic.test.tsx +25 -47
  209. package/test/rendering/code.test.tsx +3 -3
  210. package/test/rendering/formatting.test.tsx +487 -0
  211. package/test/rendering/indent.test.tsx +42 -529
  212. package/test/rendering/memoization.test.tsx +30 -0
  213. package/test/split-props.test.ts +87 -0
  214. package/test/stc.test.tsx +29 -8
  215. package/test/symbols.test.ts +132 -3
  216. package/test/utils.test.tsx +129 -20
  217. package/testing/extend-expect.ts +14 -4
  218. package/testing/render.ts +2 -4
  219. package/testing/vitest.d.ts +6 -1
  220. package/vitest.config.ts +1 -1
  221. package/dist/src/context/indent.d.ts +0 -5
  222. package/dist/src/context/indent.d.ts.map +0 -1
  223. package/dist/src/context/indent.js +0 -8
  224. package/dist/src/context/indent.js.map +0 -1
  225. package/dist/test/rendering/linebreaks.test.d.ts +0 -2
  226. package/dist/test/rendering/linebreaks.test.d.ts.map +0 -1
  227. package/src/context/indent.ts +0 -17
  228. package/test/rendering/linebreaks.test.tsx +0 -72
package/package.json CHANGED
@@ -1,27 +1,24 @@
1
1
  {
2
2
  "name": "@alloy-js/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "",
5
5
  "main": "./dist/src/index.js",
6
6
  "exports": {
7
7
  ".": {
8
- "development": "./src/index.ts",
8
+ "browser": "./dist/src/index.browser.js",
9
9
  "import": "./dist/src/index.js"
10
10
  },
11
11
  "./jsx-runtime": {
12
- "development": "./src/jsx-runtime.ts",
12
+ "types": "./dist/src/jsx-runtime.d.ts",
13
13
  "import": "./dist/src/jsx-runtime.js"
14
14
  },
15
15
  "./testing": {
16
- "development": "./testing/index.ts",
17
16
  "import": "./dist/testing/index.js"
18
17
  },
19
18
  "./stc": {
20
- "development": "./src/components/stc/index.ts",
21
19
  "import": "./dist/src/components/stc/index.js"
22
20
  },
23
21
  "./components": {
24
- "development": "./src/components/index.ts",
25
22
  "import": "./dist/src/components/index.js"
26
23
  }
27
24
  },
@@ -39,7 +36,8 @@
39
36
  "chalk": "^5.3.0",
40
37
  "cli-table3": "^0.6.5",
41
38
  "pathe": "^1.1.2",
42
- "@alloy-js/babel-preset": "~0.1.1"
39
+ "prettier": "^3.5.1",
40
+ "@alloy-js/babel-preset": "~0.2.0"
43
41
  },
44
42
  "devDependencies": {
45
43
  "@babel/cli": "^7.24.7",
@@ -47,8 +45,9 @@
47
45
  "@rollup/plugin-babel": "^6.0.4",
48
46
  "@rollup/plugin-typescript": "^11.1.6",
49
47
  "concurrently": "^8.2.2",
50
- "typescript": "^5.5.4",
51
- "vitest": "^3.0.4"
48
+ "typescript": "^5.7.3",
49
+ "vitest": "^3.0.4",
50
+ "vite": "^6.0.1"
52
51
  },
53
52
  "type": "module",
54
53
  "scripts": {
package/src/binder.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  ShallowRef,
8
8
  shallowRef,
9
9
  } from "@vue/reactivity";
10
+ import { useBinder } from "./context/binder.js";
10
11
  import { useMemberScope } from "./context/member-scope.js";
11
12
  import { useScope } from "./context/scope.js";
12
13
  import { memo, untrack } from "./jsx-runtime.js";
@@ -223,6 +224,21 @@ export interface OutputScope {
223
224
  getSymbolNames(): Set<string>;
224
225
  }
225
226
 
227
+ export type CreateSymbolOptions<T extends OutputSymbol = OutputSymbol> = {
228
+ name: string;
229
+ scope?: OutputScope;
230
+ refkey?: Refkey;
231
+ flags?: OutputSymbolFlags;
232
+ } & Omit<T, keyof OutputSymbol>;
233
+
234
+ export type CreateScopeOptions<T extends OutputScope = OutputScope> = {
235
+ kind: T["kind"];
236
+ name: string;
237
+ parent?: OutputScope | undefined;
238
+ flags?: OutputScopeFlags;
239
+ owner?: OutputSymbol;
240
+ } & Omit<T, keyof OutputScope>;
241
+
226
242
  /**
227
243
  * The binder tracks all output scopes and symbols. Scopes are nested containers
228
244
  * for symbols.
@@ -241,28 +257,19 @@ export interface Binder {
241
257
  * Create a new scope. The scope will be added to the parent scope's children.
242
258
  * The returned scope object is reactive.
243
259
  */
244
- createScope<T extends OutputScope>(
245
- args: {
246
- kind: T["kind"];
247
- name: string;
248
- parent?: OutputScope | undefined;
249
- flags?: OutputScopeFlags;
250
- owner?: OutputSymbol;
251
- } & Omit<T, keyof OutputScope>,
252
- ): T;
260
+ createScope<T extends OutputScope>(args: CreateScopeOptions<T>): T;
253
261
 
254
262
  /**
255
263
  * Create a new symbol. The symbol will be added to the parent scope's symbols.
256
264
  * The returned symbol object is reactive.
257
265
  */
258
- createSymbol<T extends OutputSymbol>(
259
- args: {
260
- name: string;
261
- scope?: OutputScope;
262
- refkey?: unknown;
263
- flags?: OutputSymbolFlags;
264
- } & Omit<T, keyof OutputSymbol>,
265
- ): T;
266
+ createSymbol<T extends OutputSymbol>(args: CreateSymbolOptions<T>): T;
267
+
268
+ /**
269
+ * Delete the given symbol. The symbol will be removed from its parent's
270
+ * scope. Any resolutions to this symbol will become undefined.
271
+ */
272
+ deleteSymbol(symbol: OutputSymbol): void;
266
273
 
267
274
  /**
268
275
  * Instantiate the static members of a symbol into the instance members of
@@ -414,6 +421,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
414
421
  const binder: Binder = {
415
422
  createScope,
416
423
  createSymbol,
424
+ deleteSymbol,
417
425
  resolveDeclarationByKey,
418
426
  addStaticMembersToSymbol,
419
427
  addInstanceMembersToSymbol,
@@ -451,15 +459,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
451
459
 
452
460
  return binder;
453
461
 
454
- function createScope<T extends OutputScope>(
455
- args: {
456
- kind: string;
457
- name: string;
458
- parent?: OutputScope;
459
- flags?: OutputScopeFlags;
460
- owner?: OutputSymbol;
461
- } & Omit<T, keyof OutputScope>,
462
- ): T {
462
+ function createScope<T extends OutputScope>(args: CreateScopeOptions<T>): T {
463
463
  const {
464
464
  kind,
465
465
  name,
@@ -518,12 +518,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
518
518
  }
519
519
 
520
520
  function createSymbol<T extends OutputSymbol>(
521
- args: {
522
- name: string;
523
- scope?: OutputScope;
524
- refkey?: Refkey;
525
- flags?: OutputSymbolFlags;
526
- } & Omit<T, keyof OutputSymbol>,
521
+ args: CreateSymbolOptions<T>,
527
522
  ): T {
528
523
  const {
529
524
  name,
@@ -591,11 +586,23 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
591
586
  scope.symbolsByRefkey.set(symbol.refkey, symbol);
592
587
 
593
588
  deconflict(symbol);
594
- notifyRefkey(refkey, symbol);
589
+ notifyRefkey(symbol);
595
590
 
596
591
  return symbol;
597
592
  }
598
593
 
594
+ function deleteSymbol(symbol: OutputSymbol) {
595
+ symbol.scope.symbols.delete(symbol);
596
+
597
+ if (!refkey) {
598
+ return;
599
+ }
600
+
601
+ const resolution = waitingDeclarations.get(symbol.refkey);
602
+ if (!resolution) return;
603
+ resolution.value = undefined;
604
+ }
605
+
599
606
  function instantiateSymbolInto(source: OutputSymbol, target: OutputSymbol) {
600
607
  if (~source.flags & OutputSymbolFlags.InstanceMemberContainer) {
601
608
  throw new Error("Can only instantiate symbols with instance members");
@@ -807,24 +814,27 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
807
814
  return computed(() => cb(declSignal.value));
808
815
  }
809
816
 
810
- function notifyRefkey(
811
- refkey: Refkey | undefined,
812
- symbol: OutputSymbol,
813
- ): void {
814
- if (!refkey) return;
815
- knownDeclarations.set(refkey, symbol);
816
- if (waitingDeclarations.has(refkey)) {
817
- const signal = waitingDeclarations.get(refkey)!;
818
- signal.value = symbol;
819
- }
817
+ function notifyRefkey(symbol: OutputSymbol): void {
818
+ effect(() => {
819
+ const refkey = symbol.refkey;
820
+ if (!refkey) return;
821
+
822
+ // notify those waiting for this refkey
823
+ knownDeclarations.set(refkey, symbol);
824
+ if (waitingDeclarations.has(refkey)) {
825
+ const signal = waitingDeclarations.get(refkey)!;
826
+ signal.value = symbol;
827
+ }
820
828
 
821
- const waitingScope = waitingSymbolNames.get(symbol.scope);
822
- if (waitingScope) {
823
- const waitingName = waitingScope.get(symbol.name);
824
- if (waitingName) {
825
- waitingName.value = symbol;
829
+ // notify those waiting for this symbol name
830
+ const waitingScope = waitingSymbolNames.get(symbol.scope);
831
+ if (waitingScope) {
832
+ const waitingName = waitingScope.get(symbol.name);
833
+ if (waitingName) {
834
+ waitingName.value = symbol;
835
+ }
826
836
  }
827
- }
837
+ });
828
838
  }
829
839
 
830
840
  function findSymbolName<TSymbol extends OutputSymbol = OutputSymbol>(
@@ -949,7 +959,7 @@ export function resolve<
949
959
  TScope extends OutputScope,
950
960
  TSymbol extends OutputSymbol,
951
961
  >(refkey: Refkey): Ref<ResolutionResult<TScope, TSymbol>> {
952
- const scope = useScope();
962
+ const scope = useScope() ?? useBinder().globalScope;
953
963
  const memberScope = useMemberScope();
954
964
  const binder = scope.binder;
955
965
 
package/src/code.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  // this code is split into a tokenizer and a parser of sorts because I feel like
2
2
  // it should be psosible to share logic between this and the babel transform, but
3
3
  // this is an exercise for the future.
4
- import { Child, Indent } from "@alloy-js/core";
5
-
4
+ import { hbr, indent } from "./components/stc/index.js";
5
+ import { Child, Children } from "./jsx-runtime.js";
6
6
  interface IndentLevelData {
7
7
  kind: "indent";
8
- children: (string | Child | IndentLevelData)[];
8
+ children: (string | Children | IndentLevelData)[];
9
9
  pendingLines: string[];
10
10
  }
11
11
  export function code(
12
12
  template: TemplateStringsArray,
13
- ...substitutions: Child[]
13
+ ...substitutions: Children[]
14
14
  ) {
15
15
  const indentNodes: IndentLevelData[] = [
16
16
  {
@@ -39,6 +39,8 @@ export function code(
39
39
  popIndent();
40
40
  flushLines();
41
41
 
42
+ return childNodesFor(indentNodes[0]);
43
+
42
44
  function childNodesFor(indentNode: IndentLevelData): Child[] {
43
45
  return indentNode.children.map((child) => {
44
46
  if (
@@ -46,16 +48,13 @@ export function code(
46
48
  child !== null &&
47
49
  (child as any).kind === "indent"
48
50
  ) {
49
- return () =>
50
- Indent({ children: childNodesFor(child as IndentLevelData) });
51
+ return indent({ children: childNodesFor(child as IndentLevelData) });
51
52
  } else {
52
53
  return child as Child;
53
54
  }
54
55
  });
55
56
  }
56
57
 
57
- return childNodesFor(indentNodes[0]);
58
-
59
58
  function pushIndent() {
60
59
  flushLines();
61
60
  const newIndent: IndentLevelData = {
@@ -63,7 +62,7 @@ export function code(
63
62
  children: [],
64
63
  pendingLines: [""],
65
64
  };
66
- indentNodes.at(-1)!.children.push(newIndent as any);
65
+ indentNodes.at(-1)!.children.push(newIndent);
67
66
  indentNodes.push(newIndent);
68
67
  }
69
68
 
@@ -84,7 +83,13 @@ export function code(
84
83
  }
85
84
  function flushLines() {
86
85
  const currentIndent = indentNodes.at(-1)!;
87
- currentIndent.children.push(currentIndent.pendingLines.join("\n"));
86
+ currentIndent.children.push(
87
+ ...currentIndent.pendingLines
88
+ .map((str, index) =>
89
+ index < currentIndent.pendingLines.length - 1 ? [str, hbr()] : [str],
90
+ )
91
+ .flat(),
92
+ );
88
93
  currentIndent.pendingLines = [];
89
94
  }
90
95
  }
@@ -103,12 +108,12 @@ interface LineToken extends ChildTokenBase {
103
108
 
104
109
  interface OtherToken extends ChildTokenBase {
105
110
  kind: "other";
106
- value: Child;
111
+ value: Children;
107
112
  }
108
113
 
109
114
  function* childTokens(
110
115
  template: TemplateStringsArray,
111
- substitutions: Child[],
116
+ substitutions: Children[],
112
117
  ): IterableIterator<ChildToken> {
113
118
  let newline = false;
114
119
  const indentStack: { level: number; literalIndent: string }[] = [
@@ -0,0 +1,44 @@
1
+ import { Children, childrenArray, computed, Indent } from "@alloy-js/core";
2
+
3
+ export interface BlockProps {
4
+ /**
5
+ * The opening punctuation of the block. Defaults to "\{".
6
+ */
7
+ opener?: string;
8
+
9
+ /**
10
+ * The closing punctuation of the block. Defaults to "\}".
11
+ */
12
+ closer?: string;
13
+
14
+ /**
15
+ * Whether the block starts on a new line. When true, a hardline is added
16
+ * prior to the block.
17
+ */
18
+ newline?: boolean;
19
+
20
+ /** The block's contents */
21
+ children?: Children;
22
+ }
23
+
24
+ /**
25
+ * Create an indented block of source text. The block has `opener` text which is
26
+ * added prior to the block, which defaults to "\{", and `closer` text which is
27
+ * added after the block, which defaults to "\}".
28
+ */
29
+ export function Block(props: BlockProps) {
30
+ const childCount = computed(() => childrenArray(() => props.children).length);
31
+ return (
32
+ <group>
33
+ {props.newline && <br />}
34
+ {props.opener ?? "{"}
35
+ <Indent break={childCount.value > 0 ? "hard" : "soft"}>
36
+ {props.children}
37
+ </Indent>
38
+ {childCount.value > 0 ?
39
+ <hbr />
40
+ : <sbr />}
41
+ {props.closer ?? "}"}
42
+ </group>
43
+ );
44
+ }
@@ -2,7 +2,7 @@ import { OutputSymbol } from "../binder.js";
2
2
  import { useContext } from "../context.js";
3
3
  import { BinderContext } from "../context/binder.js";
4
4
  import { DeclarationContext } from "../context/declaration.js";
5
- import { Children } from "../jsx-runtime.js";
5
+ import { Children, onCleanup } from "../jsx-runtime.js";
6
6
  import { Refkey, refkey } from "../refkey.js";
7
7
 
8
8
  export interface DeclarationProps {
@@ -45,9 +45,15 @@ export function Declaration(props: DeclarationProps) {
45
45
  name: props.name!,
46
46
  refkey: rk,
47
47
  });
48
+
49
+ onCleanup(() => {
50
+ binder.deleteSymbol(declaration!);
51
+ });
48
52
  }
49
53
 
50
- return <DeclarationContext.Provider value={declaration}>
51
- {props.children}
52
- </DeclarationContext.Provider>;
54
+ return (
55
+ <DeclarationContext.Provider value={declaration}>
56
+ {props.children}
57
+ </DeclarationContext.Provider>
58
+ );
53
59
  }
@@ -0,0 +1,81 @@
1
+ import { Children, memo } from "@alloy-js/core/jsx-runtime";
2
+ import { isRef, Ref } from "@vue/reactivity";
3
+ import { mapJoin } from "../utils.js";
4
+ import { BaseListProps, baseListPropsToMapJoinArgs } from "./List.jsx";
5
+
6
+ export type ForCallbackArgs<T> =
7
+ T extends Ref<infer U> ? ForCallbackArgs<U>
8
+ : T extends () => infer U ? ForCallbackArgs<U>
9
+ : T extends (infer U)[] ? [value: U]
10
+ : T extends Map<infer U, infer V> ? [key: U, value: V]
11
+ : T extends Set<infer U> ? [value: U]
12
+ : [];
13
+
14
+ export interface ForProps<
15
+ T extends
16
+ | ForSupportedCollections
17
+ | (() => ForSupportedCollections)
18
+ | Ref<ForSupportedCollections>,
19
+ U extends Children,
20
+ > extends BaseListProps {
21
+ /**
22
+ * The array to iterate over.
23
+ */
24
+ each: T;
25
+
26
+ /**
27
+ * A function to call for each item.
28
+ */
29
+ children: (...args: [...ForCallbackArgs<T>, index: number]) => U;
30
+ }
31
+
32
+ export type ForSupportedCollections = any[] | Map<any, any> | Set<any>;
33
+ /**
34
+ * The For component iterates over the provided array and invokes the child
35
+ * callback for each item. It can optionally be provided with a `joiner` which
36
+ * is placed between each item, and an `ender` which is placed after the last
37
+ * item when there is at least one item.
38
+ *
39
+ * @example
40
+ *
41
+ * ```tsx
42
+ * const items = ["apple", "pear", "plum"];
43
+ * return <For each={items}>
44
+ * {(item) => <>Fruit: {item}</>}
45
+ * </For>
46
+ * ```
47
+ *
48
+ * @remarks
49
+ *
50
+ * When the `each` prop is a reactive (e.g. a reactive array, or ref to an
51
+ * array), `For` will automatically update when the array changes. When doing
52
+ * so, it will attempt to avoid re-rendering items which have not changed. For
53
+ * example, when appending an item to a reactive array, existing items will not
54
+ * be re-rendered. Note that presently the implementation is fairly simple -
55
+ * when making modifications to the middle of an array it likely that every
56
+ * element after the modification will be rerendered.
57
+ *
58
+ * @see {@link (mapJoin:1)} for mapping arrays to elements outside of JSX templates.
59
+ */
60
+ export function For<
61
+ T extends
62
+ | ForSupportedCollections
63
+ | (() => ForSupportedCollections)
64
+ | Ref<ForSupportedCollections>,
65
+ U extends Children,
66
+ >(props: ForProps<T, U>) {
67
+ const cb = props.children;
68
+ const options = baseListPropsToMapJoinArgs(props);
69
+ options.skipFalsy = true;
70
+ return memo(() => {
71
+ const maybeRef = props.each;
72
+
73
+ return (mapJoin as any)(
74
+ typeof maybeRef === "function" ? maybeRef : (
75
+ () => (isRef(maybeRef) ? maybeRef.value : maybeRef)
76
+ ),
77
+ cb,
78
+ options,
79
+ );
80
+ });
81
+ }
@@ -1,33 +1,26 @@
1
- import { useContext } from "../context.js";
2
- import { IndentContext } from "../context/indent.js";
3
- import { Children } from "../jsx-runtime.js";
1
+ import { Children } from "@alloy-js/core/jsx-runtime";
4
2
 
5
3
  export interface IndentProps {
6
- children?: Children;
7
- indent?: string;
4
+ children: Children;
5
+ nobreak?: boolean;
6
+ break?: "space" | "soft" | "hard";
7
+ trailingBreak?: boolean;
8
8
  }
9
-
10
- export interface IndentState {
11
- level: number;
12
- indent: string;
13
- indentString: string; // awful name
14
- noLeading?: boolean;
15
- }
16
-
17
9
  export function Indent(props: IndentProps) {
18
- const previousIndent = useContext(IndentContext) ?? {
19
- level: 0,
20
- indent: props.indent ?? " ",
21
- indentString: "",
22
- };
23
-
24
- const level = previousIndent.level + 1;
25
-
26
- const currentIndent = {
27
- level,
28
- indent: props.indent ?? previousIndent.indent,
29
- indentString: (props.indent ?? previousIndent.indent).repeat(level),
30
- };
10
+ const breakStyle = props.break ?? "hard";
11
+ const breakElem =
12
+ props.nobreak ? ""
13
+ : breakStyle === "hard" ? <hbr />
14
+ : breakStyle === "soft" ? <sbr />
15
+ : <br />;
31
16
 
32
- return <IndentContext.Provider value={currentIndent}>{props.children}</IndentContext.Provider>;
17
+ return (
18
+ <>
19
+ <indent>
20
+ {breakElem}
21
+ {props.children}
22
+ </indent>
23
+ {props.trailingBreak && breakElem}
24
+ </>
25
+ );
33
26
  }
@@ -0,0 +1,94 @@
1
+ import { Children, memo, splitProps } from "@alloy-js/core/jsx-runtime";
2
+ import { childrenArray, JoinOptions } from "../utils.js";
3
+ import { For } from "./For.jsx";
4
+
5
+ export type BreakKind = "none" | "space" | "soft" | "hard" | "literal";
6
+
7
+ export interface BaseListProps {
8
+ /** Text to place between each element */
9
+ joiner?: Children;
10
+
11
+ /** Place a comma between each element */
12
+ comma?: boolean;
13
+
14
+ /** Place a semicolon between each element */
15
+ semicolon?: boolean;
16
+
17
+ line?: boolean;
18
+ softline?: boolean;
19
+ hardline?: boolean;
20
+ literalline?: boolean;
21
+ space?: boolean;
22
+
23
+ /**
24
+ * Text to place at the end of the list when there is at least one item. If
25
+ * set to true, the joiner is used.
26
+ **/
27
+ ender?: Children;
28
+
29
+ /**
30
+ * Place the join punctuation at the end, but without a line break.
31
+ */
32
+ enderPunctuation?: boolean;
33
+ }
34
+
35
+ export function baseListPropsToMapJoinArgs(props: BaseListProps): JoinOptions {
36
+ let joiner, punctuation;
37
+ if ("joiner" in props) {
38
+ joiner = props.joiner;
39
+ } else {
40
+ punctuation =
41
+ props.comma ? ","
42
+ : props.semicolon ? ";"
43
+ : "";
44
+
45
+ joiner = (
46
+ <>
47
+ {punctuation}
48
+ {props.softline ?
49
+ <sbr />
50
+ : props.hardline ?
51
+ <hbr />
52
+ : props.literalline ?
53
+ <lbr />
54
+ : props.line ?
55
+ <br />
56
+ : props.space ?
57
+ <> </>
58
+ : <hbr />}
59
+ </>
60
+ );
61
+ }
62
+
63
+ const ender =
64
+ "ender" in props ? props.ender
65
+ : props.enderPunctuation ? punctuation
66
+ : undefined;
67
+
68
+ return { joiner, ender };
69
+ }
70
+
71
+ export interface ListProps extends BaseListProps {
72
+ children?: Children;
73
+ }
74
+
75
+ /**
76
+ * Create a list of children with text between each child. The text to join with
77
+ * is specified by providing either `joiner` children, or providing boolean
78
+ * props for the punctuation and line breaks. The default joiner is no
79
+ * punctuation and a hard break. The `ender` prop can provide text to place at
80
+ * the end of the list when there is at least one child.
81
+ */
82
+ export function List(props: ListProps) {
83
+ const [rest, forProps] = splitProps(props, ["children"]);
84
+ const resolvedChildren = memo(() =>
85
+ childrenArray(() => rest.children, {
86
+ preserveFragments: true,
87
+ }),
88
+ );
89
+ return (
90
+ <For each={resolvedChildren} {...forProps}>
91
+ {(child) => child}
92
+ </For>
93
+ );
94
+ }
@@ -50,13 +50,16 @@ export function MemberDeclaration(props: MemberDeclarationProps) {
50
50
  declaration = binder.createSymbol({
51
51
  name: props.name!,
52
52
  refkey: rk,
53
- flags: props.static ?
54
- OutputSymbolFlags.StaticMember
55
- : OutputSymbolFlags.InstanceMember,
53
+ flags:
54
+ props.static ?
55
+ OutputSymbolFlags.StaticMember
56
+ : OutputSymbolFlags.InstanceMember,
56
57
  });
57
58
  }
58
59
 
59
- return <MemberDeclarationContext.Provider value={declaration}>
60
- {props.children}
61
- </MemberDeclarationContext.Provider>;
60
+ return (
61
+ <MemberDeclarationContext.Provider value={declaration}>
62
+ {props.children}
63
+ </MemberDeclarationContext.Provider>
64
+ );
62
65
  }
@@ -34,7 +34,9 @@ export function MemberScope(props: MemberScopeProps) {
34
34
  instanceMembers: props.owner.instanceMemberScope,
35
35
  staticMembers: props.owner.staticMemberScope,
36
36
  };
37
- return <MemberScopeContext.Provider value={context}>
37
+ return (
38
+ <MemberScopeContext.Provider value={context}>
38
39
  {props.children}
39
- </MemberScopeContext.Provider>;
40
+ </MemberScopeContext.Provider>
41
+ );
40
42
  }