@alloy-js/core 0.6.0 → 0.8.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 (137) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/babel.config.cjs +1 -4
  3. package/dist/src/binder.d.ts +15 -13
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +34 -34
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/code.d.ts +11 -2
  8. package/dist/src/code.d.ts.map +1 -1
  9. package/dist/src/code.js +27 -2
  10. package/dist/src/code.js.map +1 -1
  11. package/dist/src/components/Block.d.ts +2 -2
  12. package/dist/src/components/Block.d.ts.map +1 -1
  13. package/dist/src/components/Block.js +6 -5
  14. package/dist/src/components/Block.js.map +1 -1
  15. package/dist/src/components/Declaration.d.ts +31 -7
  16. package/dist/src/components/Declaration.d.ts.map +1 -1
  17. package/dist/src/components/Declaration.js +15 -7
  18. package/dist/src/components/Declaration.js.map +1 -1
  19. package/dist/src/components/For.d.ts +6 -0
  20. package/dist/src/components/For.d.ts.map +1 -1
  21. package/dist/src/components/For.js +2 -3
  22. package/dist/src/components/For.js.map +1 -1
  23. package/dist/src/components/Indent.d.ts +29 -1
  24. package/dist/src/components/Indent.d.ts.map +1 -1
  25. package/dist/src/components/Indent.js +7 -2
  26. package/dist/src/components/Indent.js.map +1 -1
  27. package/dist/src/components/List.d.ts +7 -3
  28. package/dist/src/components/List.d.ts.map +1 -1
  29. package/dist/src/components/List.js +1 -16
  30. package/dist/src/components/List.js.map +1 -1
  31. package/dist/src/components/MemberDeclaration.d.ts +35 -5
  32. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  33. package/dist/src/components/MemberDeclaration.js +18 -7
  34. package/dist/src/components/MemberDeclaration.js.map +1 -1
  35. package/dist/src/components/MemberScope.d.ts +2 -0
  36. package/dist/src/components/MemberScope.d.ts.map +1 -1
  37. package/dist/src/components/MemberScope.js +2 -0
  38. package/dist/src/components/MemberScope.js.map +1 -1
  39. package/dist/src/components/Prose.d.ts +10 -0
  40. package/dist/src/components/Prose.d.ts.map +1 -0
  41. package/dist/src/components/Prose.js +23 -0
  42. package/dist/src/components/Prose.js.map +1 -0
  43. package/dist/src/components/Scope.d.ts +33 -2
  44. package/dist/src/components/Scope.d.ts.map +1 -1
  45. package/dist/src/components/Scope.js +20 -4
  46. package/dist/src/components/Scope.js.map +1 -1
  47. package/dist/src/components/SourceFile.d.ts +5 -0
  48. package/dist/src/components/SourceFile.d.ts.map +1 -1
  49. package/dist/src/components/SourceFile.js +10 -1
  50. package/dist/src/components/SourceFile.js.map +1 -1
  51. package/dist/src/components/index.d.ts +1 -0
  52. package/dist/src/components/index.d.ts.map +1 -1
  53. package/dist/src/components/index.js +1 -0
  54. package/dist/src/components/index.js.map +1 -1
  55. package/dist/src/components/stc/index.d.ts +19 -95
  56. package/dist/src/components/stc/index.d.ts.map +1 -1
  57. package/dist/src/components/stc/index.js +3 -6
  58. package/dist/src/components/stc/index.js.map +1 -1
  59. package/dist/src/components/stc/sti.d.ts +9 -0
  60. package/dist/src/components/stc/sti.d.ts.map +1 -0
  61. package/dist/src/components/stc/sti.js +10 -0
  62. package/dist/src/components/stc/sti.js.map +1 -0
  63. package/dist/src/context/assignment.d.ts +6 -0
  64. package/dist/src/context/assignment.d.ts.map +1 -1
  65. package/dist/src/context/assignment.js +7 -0
  66. package/dist/src/context/assignment.js.map +1 -1
  67. package/dist/src/context.d.ts +2 -0
  68. package/dist/src/context.d.ts.map +1 -1
  69. package/dist/src/context.js +12 -9
  70. package/dist/src/context.js.map +1 -1
  71. package/dist/src/index.d.ts +1 -0
  72. package/dist/src/index.d.ts.map +1 -1
  73. package/dist/src/index.js +1 -0
  74. package/dist/src/index.js.map +1 -1
  75. package/dist/src/jsx-runtime.d.ts +98 -1
  76. package/dist/src/jsx-runtime.d.ts.map +1 -1
  77. package/dist/src/jsx-runtime.js +46 -1
  78. package/dist/src/jsx-runtime.js.map +1 -1
  79. package/dist/src/render.d.ts.map +1 -1
  80. package/dist/src/render.js +12 -0
  81. package/dist/src/render.js.map +1 -1
  82. package/dist/src/stc.d.ts +5 -7
  83. package/dist/src/stc.d.ts.map +1 -1
  84. package/dist/src/stc.js +11 -23
  85. package/dist/src/stc.js.map +1 -1
  86. package/dist/src/sti.d.ts +11 -0
  87. package/dist/src/sti.d.ts.map +1 -0
  88. package/dist/src/sti.js +31 -0
  89. package/dist/src/sti.js.map +1 -0
  90. package/dist/src/tap.d.ts +69 -6
  91. package/dist/src/tap.d.ts.map +1 -1
  92. package/dist/src/tap.js +70 -0
  93. package/dist/src/tap.js.map +1 -1
  94. package/dist/src/utils.d.ts +5 -0
  95. package/dist/src/utils.d.ts.map +1 -1
  96. package/dist/src/utils.js +20 -0
  97. package/dist/src/utils.js.map +1 -1
  98. package/dist/src/write-output.js +3 -3
  99. package/dist/src/write-output.js.map +1 -1
  100. package/dist/test/components/prose.test.d.ts +2 -0
  101. package/dist/test/components/prose.test.d.ts.map +1 -0
  102. package/dist/test/props-with-defaults.test.d.ts +2 -0
  103. package/dist/test/props-with-defaults.test.d.ts.map +1 -0
  104. package/dist/tsconfig.tsbuildinfo +1 -1
  105. package/package.json +3 -3
  106. package/src/binder.ts +46 -40
  107. package/src/code.ts +37 -3
  108. package/src/components/Block.tsx +3 -6
  109. package/src/components/Declaration.tsx +43 -11
  110. package/src/components/For.tsx +10 -3
  111. package/src/components/Indent.tsx +38 -5
  112. package/src/components/List.tsx +14 -40
  113. package/src/components/MemberDeclaration.tsx +51 -12
  114. package/src/components/MemberScope.tsx +2 -0
  115. package/src/components/Prose.tsx +35 -0
  116. package/src/components/Scope.tsx +45 -5
  117. package/src/components/SourceFile.tsx +10 -0
  118. package/src/components/index.tsx +1 -0
  119. package/src/components/stc/index.ts +3 -6
  120. package/src/components/stc/sti.ts +10 -0
  121. package/src/context/assignment.ts +7 -1
  122. package/src/context.ts +15 -11
  123. package/src/index.ts +1 -0
  124. package/src/jsx-runtime.ts +162 -0
  125. package/src/render.ts +14 -0
  126. package/src/stc.ts +35 -56
  127. package/src/sti.ts +63 -0
  128. package/src/tap.ts +69 -6
  129. package/src/{utils.ts → utils.tsx} +45 -0
  130. package/src/write-output.ts +3 -3
  131. package/temp/api.json +1563 -393
  132. package/test/components/declaration.test.tsx +1 -1
  133. package/test/components/prose.test.tsx +36 -0
  134. package/test/components/source-file.test.tsx +17 -0
  135. package/test/props-with-defaults.test.ts +97 -0
  136. package/test/symbols.test.ts +14 -33
  137. package/vitest.config.ts +2 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alloy-js/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "main": "./dist/src/index.js",
6
6
  "exports": {
@@ -42,12 +42,12 @@
42
42
  "devDependencies": {
43
43
  "@babel/cli": "^7.24.7",
44
44
  "@microsoft/api-extractor": "^7.47.7",
45
- "@rollup/plugin-babel": "^6.0.4",
46
45
  "@rollup/plugin-typescript": "^11.1.6",
47
46
  "concurrently": "^8.2.2",
48
47
  "typescript": "^5.7.3",
49
48
  "vitest": "^3.0.4",
50
- "vite": "^6.0.1"
49
+ "vite": "^6.0.1",
50
+ "@alloy-js/rollup-plugin": "~0.1.0"
51
51
  },
52
52
  "type": "module",
53
53
  "scripts": {
package/src/binder.ts CHANGED
@@ -99,9 +99,9 @@ export interface OutputSymbol {
99
99
  binder: Binder;
100
100
 
101
101
  /**
102
- * A unique value that references this symbol.
102
+ * The unique values that reference this symbol.
103
103
  */
104
- refkey: Refkey;
104
+ refkeys: Refkey[];
105
105
 
106
106
  /**
107
107
  * The instance members available on this symbol.
@@ -127,6 +127,11 @@ export interface OutputSymbol {
127
127
  * one static member symbol in the output (i.e., the symbol is unique).
128
128
  */
129
129
  staticMemberScope?: OutputScope;
130
+
131
+ /**
132
+ * Additional custom metadata about this symbol.
133
+ */
134
+ metadata: Record<string, unknown>;
130
135
  }
131
136
 
132
137
  /**
@@ -175,7 +180,7 @@ export interface OutputScope {
175
180
  * The kind of scope. Subtypes will likely provide a set of known scope kinds.
176
181
  * The kind is not used by the binder itself.
177
182
  */
178
- kind: string;
183
+ kind?: string;
179
184
 
180
185
  /**
181
186
  * The name of the scope.
@@ -227,8 +232,9 @@ export interface OutputScope {
227
232
  export type CreateSymbolOptions<T extends OutputSymbol = OutputSymbol> = {
228
233
  name: string;
229
234
  scope?: OutputScope;
230
- refkey?: Refkey;
235
+ refkey?: Refkey | Refkey[];
231
236
  flags?: OutputSymbolFlags;
237
+ metadata?: Record<string, unknown>;
232
238
  } & Omit<T, keyof OutputSymbol>;
233
239
 
234
240
  export type CreateScopeOptions<T extends OutputScope = OutputScope> = {
@@ -237,6 +243,7 @@ export type CreateScopeOptions<T extends OutputScope = OutputScope> = {
237
243
  parent?: OutputScope | undefined;
238
244
  flags?: OutputScopeFlags;
239
245
  owner?: OutputSymbol;
246
+ metadata?: Record<string, unknown>;
240
247
  } & Omit<T, keyof OutputScope>;
241
248
 
242
249
  /**
@@ -369,15 +376,11 @@ export interface Binder {
369
376
  * When we resolve the refkey for `bar` from within `namespace scope 2`, we will get the following
370
377
  * resolution result:
371
378
  *
372
- * **targetDeclaration**: symbol bar, the symbol we resolved.
373
- *
374
- * **commonScope**: global scope, because this is the most specific scope that contains both the declaration and the reference.
375
- *
376
- * **pathUp**: [namespace scope 2], because this is the scope between the reference and the common scope.
377
- *
378
- * **pathDown**: [namespace scope 1], because this is the scope between the common scope and the declaration
379
- *
380
- * **memberPath**: [foo, bar], because we resolved a member symbol and these are the symbols that lead from the base declaration to the member symbol.
379
+ * * **targetDeclaration**: symbol bar, the symbol we resolved.
380
+ * * **commonScope**: global scope, because this is the most specific scope that contains both the declaration and the reference.
381
+ * * **pathUp**: [namespace scope 2], because this is the scope between the reference and the common scope.
382
+ * * **pathDown**: [namespace scope 1], because this is the scope between the common scope and the declaration
383
+ * * **memberPath**: [foo, bar], because we resolved a member symbol and these are the symbols that lead from the base declaration to the member symbol.
381
384
  */
382
385
  export interface ResolutionResult<
383
386
  TScope extends OutputScope,
@@ -466,6 +469,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
466
469
  parent,
467
470
  owner,
468
471
  flags = OutputScopeFlags.None,
472
+ metadata = {},
469
473
  ...rest
470
474
  } = args;
471
475
 
@@ -498,6 +502,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
498
502
  flags,
499
503
  owner,
500
504
  binder,
505
+ metadata,
501
506
  ...rest,
502
507
  getSymbolNames: symbolNames(symbols),
503
508
  }) as T;
@@ -525,9 +530,12 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
525
530
  scope = useDefaultScope(args.flags),
526
531
  refkey,
527
532
  flags = OutputSymbolFlags.None,
533
+ metadata = {},
528
534
  ...rest
529
535
  } = args;
530
536
 
537
+ const allRefkeys = [refkey ?? []].flat();
538
+
531
539
  if (!scope) {
532
540
  throw new Error(
533
541
  "No scope was provided and no scope could be found in context",
@@ -556,9 +564,10 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
556
564
  originalName: name,
557
565
  name: name,
558
566
  scope,
559
- refkey,
567
+ refkeys: allRefkeys,
560
568
  binder,
561
569
  flags,
570
+ metadata,
562
571
  ...rest,
563
572
  }) as T;
564
573
 
@@ -583,7 +592,9 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
583
592
  }
584
593
 
585
594
  scope.symbols.add(symbol);
586
- scope.symbolsByRefkey.set(symbol.refkey, symbol);
595
+ for (const refkey of allRefkeys) {
596
+ scope.symbolsByRefkey.set(refkey, symbol);
597
+ }
587
598
 
588
599
  deconflict(symbol);
589
600
  notifyRefkey(symbol);
@@ -598,9 +609,11 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
598
609
  return;
599
610
  }
600
611
 
601
- const resolution = waitingDeclarations.get(symbol.refkey);
602
- if (!resolution) return;
603
- resolution.value = undefined;
612
+ for (const refkey of symbol.refkeys) {
613
+ const resolution = waitingDeclarations.get(refkey);
614
+ if (!resolution) return;
615
+ resolution.value = undefined;
616
+ }
604
617
  }
605
618
 
606
619
  function instantiateSymbolInto(source: OutputSymbol, target: OutputSymbol) {
@@ -619,7 +632,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
619
632
  createSymbol({
620
633
  name: sym.name,
621
634
  scope: target.instanceMemberScope!,
622
- refkey: refkey(target.refkey, sym.refkey),
635
+ refkey: [refkey(target.refkeys[0], sym.refkeys[0])],
623
636
  flags: sym.flags | OutputSymbolFlags.InstanceMember,
624
637
  });
625
638
  }
@@ -693,12 +706,6 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
693
706
  targetDeclarationBase: TSymbol,
694
707
  ): ResolutionResult<TScope, TSymbol> {
695
708
  if (targetDeclarationBase.flags & OutputSymbolFlags.InstanceMember) {
696
- if (targetDeclarationBase.scope !== currentMemberScope) {
697
- throw new Error(
698
- "Cannot resolve member symbols from a different member scope",
699
- );
700
- }
701
-
702
709
  // todo: handle referencing nested objects by refkey
703
710
  return {
704
711
  pathUp: [],
@@ -816,22 +823,21 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
816
823
 
817
824
  function notifyRefkey(symbol: OutputSymbol): void {
818
825
  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
- }
826
+ for (const refkey of symbol.refkeys) {
827
+ // notify those waiting for this refkey
828
+ knownDeclarations.set(refkey, symbol);
829
+ if (waitingDeclarations.has(refkey)) {
830
+ const signal = waitingDeclarations.get(refkey)!;
831
+ signal.value = symbol;
832
+ }
828
833
 
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;
834
+ // notify those waiting for this symbol name
835
+ const waitingScope = waitingSymbolNames.get(symbol.scope);
836
+ if (waitingScope) {
837
+ const waitingName = waitingScope.get(symbol.name);
838
+ if (waitingName) {
839
+ waitingName.value = symbol;
840
+ }
835
841
  }
836
842
  }
837
843
  });
package/src/code.ts CHANGED
@@ -1,17 +1,51 @@
1
1
  // this code is split into a tokenizer and a parser of sorts because I feel like
2
- // it should be psosible to share logic between this and the babel transform, but
2
+ // it should be possible to share logic between this and the babel transform, but
3
3
  // this is an exercise for the future.
4
- import { hbr, indent } from "./components/stc/index.js";
4
+ import { hbr, indent } from "./components/stc/sti.js";
5
5
  import { Child, Children } from "./jsx-runtime.js";
6
+
7
+ export function text(
8
+ template: TemplateStringsArray,
9
+ ...substitutions: Children[]
10
+ ): Children {
11
+ const children = [];
12
+ // push the literal parts and the substitutions into the children array. The
13
+ // first part has all leading whitespace removed, the last part has all
14
+ // trailing whitespace removed, and each part in the middle replaces any
15
+ // amount of whitespace with a single space.
16
+
17
+ for (let i = 0; i < template.length; i++) {
18
+ let part = template[i];
19
+ part = part
20
+ .replace(/(^(\s*\r?\n\s*)+)|((\s*\r?\n\s*)+$)/g, "")
21
+ .replace(/(\s*\r?\n\s*)+/g, " ");
22
+ children.push(part);
23
+ if (i < substitutions.length) {
24
+ children.push(substitutions[i]);
25
+ }
26
+ }
27
+
28
+ return children;
29
+ }
30
+
6
31
  interface IndentLevelData {
7
32
  kind: "indent";
8
33
  children: (string | Children | IndentLevelData)[];
9
34
  pendingLines: string[];
10
35
  }
36
+
37
+ /**
38
+ * Turn the provided string template into Children by replacing literal line
39
+ * breaks with hardlines and automatically indenting indented content. Similar
40
+ * in spirit to the `<code>` element in HTML.
41
+ *
42
+ * @see {@link text} for a similar function which treats whitespace similar to
43
+ * JSX template bodies.
44
+ */
11
45
  export function code(
12
46
  template: TemplateStringsArray,
13
47
  ...substitutions: Children[]
14
- ) {
48
+ ): Children {
15
49
  const indentNodes: IndentLevelData[] = [
16
50
  {
17
51
  kind: "indent",
@@ -23,8 +23,8 @@ export interface BlockProps {
23
23
 
24
24
  /**
25
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 "\}".
26
+ * added prior to the block, which defaults to `"{"`, and `closer` text which is
27
+ * added after the block, which defaults to `"}"`.
28
28
  */
29
29
  export function Block(props: BlockProps) {
30
30
  const childCount = computed(() => childrenArray(() => props.children).length);
@@ -32,12 +32,9 @@ export function Block(props: BlockProps) {
32
32
  <group>
33
33
  {props.newline && <br />}
34
34
  {props.opener ?? "{"}
35
- <Indent break={childCount.value > 0 ? "hard" : "soft"}>
35
+ <Indent softline={childCount.value === 0} trailingBreak>
36
36
  {props.children}
37
37
  </Indent>
38
- {childCount.value > 0 ?
39
- <hbr />
40
- : <sbr />}
41
38
  {props.closer ?? "}"}
42
39
  </group>
43
40
  );
@@ -3,15 +3,48 @@ import { useContext } from "../context.js";
3
3
  import { BinderContext } from "../context/binder.js";
4
4
  import { DeclarationContext } from "../context/declaration.js";
5
5
  import { Children, onCleanup } from "../jsx-runtime.js";
6
- import { Refkey, refkey } from "../refkey.js";
6
+ import { Refkey } from "../refkey.js";
7
+
8
+ /**
9
+ * Create a declaration by providing an already created symbol. The symbol is
10
+ * merely exposed via {@link DeclarationContext}.
11
+ */
12
+ export interface DeclarationPropsWithSymbol {
13
+ /**
14
+ * The symbol being declared. When provided, the name, refkey, and metadata
15
+ * props are ignored.
16
+ */
17
+ symbol: OutputSymbol;
18
+
19
+ children?: Children;
20
+ }
21
+
22
+ /**
23
+ * Create a declaration by providing a symbol name and optional symbol metadata.
24
+ */
25
+ export interface DeclarationPropsWithInfo {
26
+ /**
27
+ * The name of this declaration.
28
+ */
29
+ name: string;
30
+
31
+ /**
32
+ * The unique key or array of unique keys for this declaration.
33
+ */
34
+ refkey?: Refkey | Refkey[];
35
+
36
+ /**
37
+ * Additional metadata for the declared symbol.
38
+ */
39
+ metadata?: Record<string, unknown>;
7
40
 
8
- export interface DeclarationProps {
9
- name?: string;
10
- refkey?: Refkey;
11
- symbol?: OutputSymbol;
12
41
  children?: Children;
13
42
  }
14
43
 
44
+ export type DeclarationProps =
45
+ | DeclarationPropsWithSymbol
46
+ | DeclarationPropsWithInfo;
47
+
15
48
  /**
16
49
  * Declares a symbol in the current scope for this component's children.
17
50
  *
@@ -20,11 +53,10 @@ export interface DeclarationProps {
20
53
  * This component must be called in one of two ways: with a name and an optional
21
54
  * refkey, or else passing in the symbol. When called with a name and refkey, a
22
55
  * symbol will be created in the current scope (provided by
23
- * {@link ScopeContext}) with that name and refkey. If a refkey is not provided,
24
- * `refkey(props.name)` is used.
56
+ * {@link ScopeContext}) with that name and refkey.
25
57
  *
26
58
  * When called with a symbol, that symbol is merely exposed via
27
- * {@link DeclarationContext }. It is assumed that the caller of this component
59
+ * {@link DeclarationContext}. It is assumed that the caller of this component
28
60
  * has created the symbol with the `createSymbol` API on the
29
61
  * {@link BinderContext }.
30
62
  *
@@ -37,13 +69,13 @@ export function Declaration(props: DeclarationProps) {
37
69
  }
38
70
 
39
71
  let declaration;
40
- if (props.symbol) {
72
+ if ("symbol" in props) {
41
73
  declaration = props.symbol;
42
74
  } else {
43
- const rk = props.refkey ? props.refkey : refkey(props.name);
44
75
  declaration = binder.createSymbol({
45
76
  name: props.name!,
46
- refkey: rk,
77
+ refkey: props.refkey,
78
+ metadata: props.metadata,
47
79
  });
48
80
 
49
81
  onCleanup(() => {
@@ -1,7 +1,7 @@
1
1
  import { Children, memo } from "@alloy-js/core/jsx-runtime";
2
2
  import { isRef, Ref } from "@vue/reactivity";
3
- import { mapJoin } from "../utils.js";
4
- import { BaseListProps, baseListPropsToMapJoinArgs } from "./List.jsx";
3
+ import { baseListPropsToMapJoinArgs, mapJoin } from "../utils.js";
4
+ import { BaseListProps } from "./List.jsx";
5
5
 
6
6
  export type ForCallbackArgs<T> =
7
7
  T extends Ref<infer U> ? ForCallbackArgs<U>
@@ -27,6 +27,13 @@ export interface ForProps<
27
27
  * A function to call for each item.
28
28
  */
29
29
  children: (...args: [...ForCallbackArgs<T>, index: number]) => U;
30
+
31
+ /**
32
+ * Whether to skip falsy values. By default, falsy values are mapped. However,
33
+ * when mapping children, it is useful to skip falsy values, as it enables
34
+ * omitting list elements via patterns like `{condition && <ListItem />}`.
35
+ */
36
+ skipFalsy?: boolean;
30
37
  }
31
38
 
32
39
  export type ForSupportedCollections = any[] | Map<any, any> | Set<any>;
@@ -66,7 +73,7 @@ export function For<
66
73
  >(props: ForProps<T, U>) {
67
74
  const cb = props.children;
68
75
  const options = baseListPropsToMapJoinArgs(props);
69
- options.skipFalsy = true;
76
+ options.skipFalsy = props.skipFalsy;
70
77
  return memo(() => {
71
78
  const maybeRef = props.each;
72
79
 
@@ -2,17 +2,50 @@ import { Children } from "@alloy-js/core/jsx-runtime";
2
2
 
3
3
  export interface IndentProps {
4
4
  children: Children;
5
+ /**
6
+ * Don't include a line break. The new indentation level will start after the
7
+ * first linebreak within the children.
8
+ */
5
9
  nobreak?: boolean;
6
- break?: "space" | "soft" | "hard";
10
+
11
+ /**
12
+ * Use a regular line (`<br />`) to start (and optionally end with
13
+ * `trailingBreak`) the new indentation level.
14
+ */
15
+ line?: boolean;
16
+
17
+ /**
18
+ * Use a soft line (`<sbr />`) to start (and optionally end with
19
+ * `trailingBreak`) the new indentation level.
20
+ */
21
+ softline?: boolean;
22
+
23
+ /**
24
+ * Use a hard line (`<hbr />`) to start (and optionally end with
25
+ * `trailingBreak`) the new indentation level.
26
+ */
27
+ hardline?: boolean;
28
+
29
+ /**
30
+ * Place the configured line break at the end of the block after restoring the
31
+ * indentation level.
32
+ */
7
33
  trailingBreak?: boolean;
8
34
  }
35
+
36
+ /**
37
+ * Create an indented block of source text. The indented block starts a new line
38
+ * at the new indentation level and, with `trailingBreak`, ends with a new line
39
+ * after restoring the indentation level. The default line break is a hard line
40
+ * break suitable for typical blocks of statements but can be configured.
41
+ */
9
42
  export function Indent(props: IndentProps) {
10
- const breakStyle = props.break ?? "hard";
11
43
  const breakElem =
12
44
  props.nobreak ? ""
13
- : breakStyle === "hard" ? <hbr />
14
- : breakStyle === "soft" ? <sbr />
15
- : <br />;
45
+ : props.hardline ? <hbr />
46
+ : props.softline ? <sbr />
47
+ : props.line ? <br />
48
+ : <hbr />;
16
49
 
17
50
  return (
18
51
  <>
@@ -1,9 +1,7 @@
1
1
  import { Children, memo, splitProps } from "@alloy-js/core/jsx-runtime";
2
- import { childrenArray, JoinOptions } from "../utils.js";
2
+ import { childrenArray } from "../utils.js";
3
3
  import { For } from "./For.jsx";
4
4
 
5
- export type BreakKind = "none" | "space" | "soft" | "hard" | "literal";
6
-
7
5
  export interface BaseListProps {
8
6
  /** Text to place between each element */
9
7
  joiner?: Children;
@@ -14,10 +12,22 @@ export interface BaseListProps {
14
12
  /** Place a semicolon between each element */
15
13
  semicolon?: boolean;
16
14
 
15
+ /** Place a regular line (`<br />`) between each element */
17
16
  line?: boolean;
17
+
18
+ /** Place a softline (`<sbr />`) between each element */
18
19
  softline?: boolean;
20
+
21
+ /** Place a hardline (`<hbr />`) between each element */
19
22
  hardline?: boolean;
23
+
24
+ /** Place two hardlines between each element */
25
+ doubleHardline?: boolean;
26
+
27
+ /** Place a literal line (`<lbr />`) between each element */
20
28
  literalline?: boolean;
29
+
30
+ /** Place a space between each element */
21
31
  space?: boolean;
22
32
 
23
33
  /**
@@ -32,42 +42,6 @@ export interface BaseListProps {
32
42
  enderPunctuation?: boolean;
33
43
  }
34
44
 
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
45
  export interface ListProps extends BaseListProps {
72
46
  children?: Children;
73
47
  }
@@ -87,7 +61,7 @@ export function List(props: ListProps) {
87
61
  }),
88
62
  );
89
63
  return (
90
- <For each={resolvedChildren} {...forProps}>
64
+ <For each={resolvedChildren} {...forProps} skipFalsy>
91
65
  {(child) => child}
92
66
  </For>
93
67
  );
@@ -3,16 +3,54 @@ import { useContext } from "../context.js";
3
3
  import { BinderContext } from "../context/binder.js";
4
4
  import { MemberDeclarationContext } from "../context/member-declaration.js";
5
5
  import { Children } from "../jsx-runtime.js";
6
- import { Refkey, refkey } from "../refkey.js";
6
+ import { Refkey } from "../refkey.js";
7
7
 
8
- export interface MemberDeclarationProps {
9
- name?: string;
10
- refkey?: Refkey;
11
- symbol?: OutputSymbol;
12
- children?: Children;
8
+ /**
9
+ * Create a member declaration by providing a symbol name and optional symbol
10
+ * metadata.
11
+ */
12
+ export interface MemberDeclarationPropsWithInfo {
13
+ /**
14
+ * The name of this declaration.
15
+ */
16
+ name: string;
17
+
18
+ /**
19
+ * The refkey or array refkeys for this declaration.
20
+ */
21
+ refkey?: Refkey | Refkey[];
22
+
23
+ /**
24
+ * Additional metadata for the declared symbol.
25
+ */
26
+ metadata?: Record<string, unknown>;
27
+
28
+ /**
29
+ * Whether this is a static member. If not provided, the member is an instance
30
+ * member.
31
+ */
13
32
  static?: boolean;
33
+ children?: Children;
14
34
  }
15
35
 
36
+ /**
37
+ * Create a declaration by providing an already created symbol. The symbol is
38
+ * merely exposed via {@link DeclarationContext}.
39
+ */
40
+ export interface MemberDeclarationPropsWithSymbol {
41
+ /**
42
+ * The symbol being declared. When provided, the name, refkey, and metadata
43
+ * props are ignored.
44
+ */
45
+ symbol: OutputSymbol;
46
+
47
+ children?: Children;
48
+ }
49
+
50
+ export type MemberDeclarationProps =
51
+ | MemberDeclarationPropsWithInfo
52
+ | MemberDeclarationPropsWithSymbol;
53
+
16
54
  /**
17
55
  * Declares a symbol in the current member scope for this component's children.
18
56
  *
@@ -38,20 +76,21 @@ export function MemberDeclaration(props: MemberDeclarationProps) {
38
76
  }
39
77
 
40
78
  let declaration;
41
- if (props.symbol) {
79
+ if ("symbol" in props && props.symbol) {
42
80
  declaration = props.symbol;
43
81
  } else {
44
- if (!props.name) {
82
+ const infoProps = props as MemberDeclarationPropsWithInfo;
83
+ if (!infoProps.name) {
45
84
  throw new Error(
46
85
  "Must provide a member name, or else provide a member symbol",
47
86
  );
48
87
  }
49
- const rk = props.refkey ? props.refkey : refkey(props.name);
50
88
  declaration = binder.createSymbol({
51
- name: props.name!,
52
- refkey: rk,
89
+ name: infoProps.name!,
90
+ refkey: infoProps.refkey,
91
+ metadata: infoProps.metadata,
53
92
  flags:
54
- props.static ?
93
+ infoProps.static ?
55
94
  OutputSymbolFlags.StaticMember
56
95
  : OutputSymbolFlags.InstanceMember,
57
96
  });
@@ -28,6 +28,8 @@ export interface MemberScopeProps {
28
28
  *
29
29
  * The member scope contains scopes for both instance and static members.
30
30
  * However, it does not affect the resolution of static members.
31
+ *
32
+ * @see {@link (MemberScopeContext:variable)}
31
33
  */
32
34
  export function MemberScope(props: MemberScopeProps) {
33
35
  const context: MemberScopeContext = {