sveld 0.32.2 → 0.32.4

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/README.md CHANGED
@@ -3,15 +3,15 @@
3
3
  [![NPM][npm]][npm-url]
4
4
  ![npm downloads to date](https://img.shields.io/npm/dt/sveld?color=262626&style=for-the-badge)
5
5
 
6
- `sveld` generates TypeScript definitions and component documentation (Markdown/JSON) for Svelte components. It analyzes props, events, slots, and other component features through static analysis. Types and signatures can be defined using [JSDoc notation](https://jsdoc.app/).
6
+ `sveld` generates TypeScript definitions and component documentation (Markdown/JSON) for Svelte components. It statically analyzes props, events, slots, and the rest. Add types with [JSDoc](https://jsdoc.app/) when inference is not enough.
7
7
 
8
- The purpose of this project is to make third party Svelte component libraries compatible with the Svelte Language Server and TypeScript with minimal effort required by the author. For example, TypeScript definitions may be used during development via intelligent code completion in Integrated Development Environments (IDEs) like VSCode.
8
+ The goal is to get third-party Svelte libraries working with the Svelte Language Server and TypeScript with minimal effort from the author. Generated `.d.ts` files give you autocomplete in VS Code and other IDEs.
9
9
 
10
10
  [Carbon Components Svelte](https://github.com/carbon-design-system/carbon-components-svelte) uses this library to auto-generate component types and API metadata.
11
11
 
12
12
  `sveld` uses the Svelte 5 compiler to parse `.svelte` files. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, Svelte 5 without runes (`export let`, `<slot>`, `$$restProps`, …), and Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …).
13
13
 
14
- For `lang="ts"` components, `sveld` preserves source-level prop type annotations when possible instead of requiring JSDoc as the primary source of truth. This includes legacy `export let` props, typed `$props()` destructuring, typed whole-object `$props()` captures, local `interface`/`type` declarations, and imported type references in emitted `.d.ts` files.
14
+ For `lang="ts"` components, `sveld` keeps source-level prop type annotations when it can, instead of forcing JSDoc. That covers legacy `export let` props, typed `$props()` destructuring, typed whole-object `$props()` captures, local `interface`/`type` declarations, and imported type references in emitted `.d.ts` files.
15
15
 
16
16
  | Syntax mode | Supported |
17
17
  | :------------------- | :-------: |
@@ -20,11 +20,11 @@ For `lang="ts"` components, `sveld` preserves source-level prop type annotations
20
20
  | Svelte 5 (non-Runes) | ✓ |
21
21
  | Svelte 5 Runes | ✓ |
22
22
 
23
- **Note** that generated `.d.ts` files extend `SvelteComponentTyped` from `svelte`, so TypeScript and the Svelte Language Server work whether consumers use Svelte 3, Svelte 4, or Svelte 5.
23
+ Generated `.d.ts` files extend `SvelteComponentTyped` from `svelte`, so TypeScript and the Svelte Language Server work whether consumers use Svelte 3, Svelte 4, or Svelte 5.
24
24
 
25
25
  ---
26
26
 
27
- Given a Svelte component, `sveld` can infer basic prop types to generate TypeScript definitions compatible with the [Svelte Language Server](https://github.com/sveltejs/language-tools):
27
+ From a Svelte component, `sveld` can infer basic prop types and emit definitions the [Svelte Language Server](https://github.com/sveltejs/language-tools) understands:
28
28
 
29
29
  **Button.svelte**
30
30
 
@@ -72,9 +72,7 @@ export default class Button extends SvelteComponentTyped<
72
72
  > {}
73
73
  ```
74
74
 
75
- Sometimes, inferring prop types is insufficient.
76
-
77
- Prop/event/slot types and signatures can be augmented using [JSDoc](https://jsdoc.app/) notations.
75
+ Inference only gets you so far. Use [JSDoc](https://jsdoc.app/) to document prop, event, and slot types when you need more precision.
78
76
 
79
77
  ```js
80
78
  /** @type {"button" | "submit" | "reset"} */
@@ -86,7 +84,7 @@ export let type = "button";
86
84
  export let primary = false;
87
85
  ```
88
86
 
89
- The accompanying JSDoc annotations would generate the following:
87
+ With JSDoc, the output looks like this:
90
88
 
91
89
  ```ts
92
90
  import type { SvelteHTMLElements } from "svelte/elements";
@@ -149,9 +147,9 @@ export default class Button extends SvelteComponentTyped<
149
147
 
150
148
  ## Approach
151
149
 
152
- `sveld` uses the Svelte compiler to statically analyze Svelte components exported from a library to generate documentation useful to the end user.
150
+ `sveld` uses the Svelte compiler to statically analyze exported components and emit docs for consumers.
153
151
 
154
- Extracted metadata include:
152
+ It extracts:
155
153
 
156
154
  - props
157
155
  - slots
@@ -160,7 +158,7 @@ Extracted metadata include:
160
158
  - context (setContext/getContext)
161
159
  - `$$restProps`
162
160
 
163
- This library adopts a progressively enhanced approach. Any property type that cannot be inferred (e.g., "hello" is a string) falls back to "any" to minimize incorrectly typed properties or signatures. To mitigate this, the library author can add JSDoc annotations to specify types that cannot be reliably inferred. This represents a progressively enhanced approach because JSDocs are comments that can be ignored by the compiler.
161
+ When inference fails, props fall back to `any` rather than guessing wrong. Authors can tighten types with JSDoc. Comments are optional from the compiler's point of view, so plain JavaScript components still parse.
164
162
 
165
163
  When both TypeScript syntax and JSDoc are present, `sveld` resolves prop types in this order:
166
164
 
@@ -169,7 +167,7 @@ When both TypeScript syntax and JSDoc are present, `sveld` resolves prop types i
169
167
  3. initializer inference
170
168
  4. `any`
171
169
 
172
- `sveld` intentionally stays AST-only. It preserves imported and local type text in generated `.d.ts` output, but it does not perform project-wide semantic resolution with the TypeScript compiler. That means opaque imported whole-object `$props()` types can be preserved in declarations without being fully expanded into JSON metadata.
170
+ `sveld` stays AST-only. It copies imported and local type text into generated `.d.ts` output but does not run project-wide semantic resolution with the TypeScript compiler. Opaque imported whole-object `$props()` types can therefore stay in declarations without being fully expanded into JSON metadata.
173
171
 
174
172
  ## Usage
175
173
 
@@ -206,7 +204,7 @@ export default defineConfig({
206
204
  });
207
205
  ```
208
206
 
209
- Since Vite uses Rollup for production builds, `sveld` also works in Rollup configurations.
207
+ Since Vite uses Rollup for production builds, the same plugin works in Rollup configs.
210
208
 
211
209
  By default, `sveld` will use the `"svelte"` field from your `package.json` to determine the entry point. You can override this by specifying an explicit `entry` option:
212
210
 
@@ -248,7 +246,7 @@ npx sveld --json --markdown
248
246
 
249
247
  ### Node.js
250
248
 
251
- You can also use `sveld` programmatically in Node.js. The package is **ESM-only**; `require("sveld")` is not supported. Use `import` or dynamic `import()`.
249
+ You can also call `sveld` from Node.js. The package is **ESM-only**; `require("sveld")` does not work. Use `import` or dynamic `import()`.
252
250
 
253
251
  If no `input` is specified, `sveld` will infer the entry point based on the `package.json#svelte` field.
254
252
 
@@ -278,7 +276,7 @@ sveld({
278
276
 
279
277
  #### `jsonOptions.outDir`
280
278
 
281
- If `json` is `true`, a `COMPONENT_API.json` file will be generated at the root of your project. This file contains documentation for all components.
279
+ With `json: true`, `sveld` writes `COMPONENT_API.json` at the project root. The file documents all components.
282
280
 
283
281
  Use the `jsonOptions.outDir` option to specify the folder for individual JSON files to be emitted.
284
282
 
@@ -295,7 +293,7 @@ sveld({
295
293
 
296
294
  ### Publishing to NPM
297
295
 
298
- TypeScript definitions are outputted to the `types` folder by default. Don't forget to include the folder in your `package.json` when publishing the package to NPM.
296
+ TypeScript definitions land in the `types` folder by default. Include that folder in `package.json` when you publish to npm.
299
297
 
300
298
  ```diff
301
299
  {
@@ -339,9 +337,7 @@ sveld({
339
337
  When `json: true` is enabled, `sveld` emits a `COMPONENT_API.json` file with schema and generator metadata plus the parsed
340
338
  component API.
341
339
 
342
- The public JSON Schema for the combined output is hosted on GitHub ([path to file](https://github.com/carbon-design-system/sveld/blob/main/schema/component-api.schema.json), [raw URL](https://raw.githubusercontent.com/carbon-design-system/sveld/main/schema/component-api.schema.json)). Use it to document or validate
343
- generated `COMPONENT_API.json` files. The schema describes the emitted metadata contract; optional fields may be absent when
344
- the parser does not have a stable source for that metadata.
340
+ The JSON Schema lives on GitHub ([path to file](https://github.com/carbon-design-system/sveld/blob/main/schema/component-api.schema.json), [raw URL](https://raw.githubusercontent.com/carbon-design-system/sveld/main/schema/component-api.schema.json)). Use it to validate generated `COMPONENT_API.json` files. Optional fields may be missing when the parser has no stable source for that metadata.
345
341
 
346
342
  ```ts
347
343
  interface ComponentApiJson {
@@ -437,12 +433,11 @@ type ComponentEvent =
437
433
  };
438
434
  ```
439
435
 
440
- `source` fields are optional and are included only when the Svelte or JavaScript AST provides stable positions. They do not
441
- include source text or raw character offsets.
436
+ `source` fields appear only when the Svelte or JavaScript AST has stable positions. They omit source text and raw character offsets.
442
437
 
443
- Note that `SourcePosition.line` is 1-based and `SourcePosition.column` is 0-based.
438
+ `SourcePosition.line` is 1-based. `SourcePosition.column` is 0-based.
444
439
 
445
- Prop metadata is additive and preserves the older public fields:
440
+ Prop metadata is additive and keeps the older public fields:
446
441
 
447
442
  - `name` is always the public prop name. For runes `$props()` aliases such as `let { class: className } = $props()`, `localName` is emitted only when the local binding differs.
448
443
  - `typeSource` identifies the conservative source of the emitted `type`: TypeScript annotation, JSDoc, initializer/default inference, other parser inference, or unknown fallback.
@@ -453,7 +448,7 @@ Prop metadata is additive and preserves the older public fields:
453
448
 
454
449
  ### `reactive`
455
450
 
456
- The `reactive` field in generated JSON is heuristic metadata. It is not a complete statement of whether a parent may use `bind:prop` in Svelte.
451
+ The `reactive` field in generated JSON is a heuristic. It does not fully answer whether a parent can use `bind:prop` in Svelte.
457
452
 
458
453
  `sveld` marks `reactive: true` when it finds internal evidence that a prop is writable, including:
459
454
 
@@ -468,7 +463,7 @@ Local variables or parameters that shadow a prop name do not count as writes to
468
463
 
469
464
  ### `binding`
470
465
 
471
- The optional `binding` field in generated JSON is explicit documentation metadata for a prop's intended `bind:` contract. It is separate from `reactive`, and it is never inferred from internal writes or `$bindable()`.
466
+ The optional `binding` field documents a prop's intended `bind:` contract. It is separate from `reactive` and is never inferred from internal writes or `$bindable()`.
472
467
 
473
468
  Use `@bindable readonly` for component-owned or output-style bindings where the consumer binds to the current value emitted by the component:
474
469
 
@@ -496,7 +491,7 @@ Use `@bindable writable` for two-way or shared state bindings where either the c
496
491
 
497
492
  Generated JSON includes `"binding": "readonly"` or `"binding": "writable"` for annotated props. Unannotated props omit the field.
498
493
 
499
- This is documentation metadata only. Generated `.svelte.d.ts` prop types are unchanged because TypeScript cannot reliably express Svelte component binding direction.
494
+ This is documentation only. Generated `.svelte.d.ts` prop types do not change. TypeScript cannot express Svelte binding direction reliably.
500
495
 
501
496
  For stable output, generated `events` arrays are emitted in deterministic sorted order.
502
497
 
@@ -516,9 +511,9 @@ export let id = `ccs-${Math.random().toString(36)}`;
516
511
  // inferred type: "string"
517
512
  ```
518
513
 
519
- Use the `@type` tag to explicitly document the type. In the following example, the `kind` property has an enumerated (enum) type.
514
+ Use the `@type` tag to document the type explicitly. In the example below, `kind` is a string union.
520
515
 
521
- For `lang="ts"` components, prefer native TypeScript annotations when they are already present. `@type` remains useful for JavaScript components, for overriding inferred types, and for cases where the AST cannot recover a more precise type.
516
+ For `lang="ts"` components, prefer native TypeScript annotations when you already have them. `@type` still helps in JavaScript components, for overriding inferred types, and when the AST cannot recover a sharper type.
522
517
 
523
518
  **Signature:**
524
519
 
@@ -550,7 +545,7 @@ For `lang="ts"` components, prefer native TypeScript annotations when they are a
550
545
  </script>
551
546
  ```
552
547
 
553
- For runes components with multiple destructured props, place JSDoc on the individual property you want to document. A declaration-level JSDoc block is only used as a fallback when the destructure exposes a single public prop.
548
+ For runes components with multiple destructured props, put JSDoc on the property you want to document. A declaration-level block is a fallback when the destructure exposes a single public prop.
554
549
 
555
550
  **Svelte 3, 4, 5 (non-Runes):**
556
551
 
@@ -570,6 +565,120 @@ For runes components with multiple destructured props, place JSDoc on the indivi
570
565
  </script>
571
566
  ```
572
567
 
568
+ #### Importing types
569
+
570
+ `sveld` supports TypeScript's [`import(...)` type syntax](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types), so a `@type` or `@typedef` can reference a type from another module without a top-level `import`. The expression is copied verbatim into the generated `.d.ts` and resolves the same way as hand-written TypeScript:
571
+
572
+ - `import("module").Type` references an exported **type**.
573
+ - `typeof import("module").value` references the type of an exported **value**.
574
+ - `import("svelte").ComponentProps<...>` and other utility types compose with imports.
575
+
576
+ This keeps third-party types (Svelte stores, another component's props, library types) out of runtime imports while still showing up in IntelliSense for consumers.
577
+
578
+ **Example:**
579
+
580
+ ```svelte
581
+ <script>
582
+ /**
583
+ * A store from `svelte/store`. No top-level `import` required.
584
+ * @type {import("svelte/store").Writable<string>}
585
+ */
586
+ export let value;
587
+
588
+ /**
589
+ * `typeof import(...)` references the type of a value export, here the
590
+ * `writable` factory itself rather than a type it exports.
591
+ * @type {typeof import("svelte/store").writable}
592
+ */
593
+ export let createStore;
594
+
595
+ /**
596
+ * Reuse another component's props with Svelte's `ComponentProps` utility.
597
+ * @type {import("svelte").ComponentProps<import("svelte").SvelteComponent>}
598
+ */
599
+ export let buttonProps;
600
+
601
+ /**
602
+ * `import(...)` works inside a `@typedef` too, which helps when typing values shared via `setContext` / `getContext`.
603
+ *
604
+ * @typedef {{ rows: import("svelte/store").Writable<string[]>; selected: import("svelte/store").Readable<number> }} TableContext
605
+ */
606
+
607
+ /** @type {TableContext} */
608
+ export let context;
609
+ </script>
610
+ ```
611
+
612
+ Output:
613
+
614
+ ```ts
615
+ export interface TableContext {
616
+ rows: import("svelte/store").Writable<string[]>;
617
+ selected: import("svelte/store").Readable<number>;
618
+ }
619
+
620
+ export type ComponentProps = {
621
+ /** @default undefined */
622
+ value: import("svelte/store").Writable<string>;
623
+ /** @default undefined */
624
+ createStore: typeof import("svelte/store").writable;
625
+ /** @default undefined */
626
+ buttonProps: import("svelte").ComponentProps<import("svelte").SvelteComponent>;
627
+ /** @default undefined */
628
+ context: TableContext;
629
+ };
630
+ ```
631
+
632
+ #### Prefer `unknown` over `any`
633
+
634
+ When a prop accepts data whose shape you do not know ahead of time, annotate it as `unknown` rather than `any`. `sveld` preserves either keyword in the emitted prop type, but they behave differently for consumers: `unknown` forces a narrowing check before use; `any` disables type checking everywhere the value flows. Reserve `any` for real escape hatches.
635
+
636
+ **Example:**
637
+
638
+ ```svelte
639
+ <script>
640
+ /**
641
+ * A value of unknown shape. Prefer `unknown` over `any`: consumers must
642
+ * narrow it before use instead of silently opting out of type checking.
643
+ *
644
+ * @type {unknown}
645
+ */
646
+ export let payload;
647
+
648
+ /**
649
+ * An escape hatch typed as `any`, shown for contrast. `any` disables type
650
+ * checking everywhere it flows, so reach for `unknown` at boundaries instead.
651
+ *
652
+ * @type {any}
653
+ */
654
+ export let raw;
655
+ </script>
656
+ ```
657
+
658
+ Output:
659
+
660
+ ```ts
661
+ export type ComponentProps = {
662
+ /** @default undefined */
663
+ payload: unknown;
664
+ /** @default undefined */
665
+ raw: any;
666
+ };
667
+ ```
668
+
669
+ Consumers must narrow an `unknown` prop before using it, while an `any` prop silently accepts anything:
670
+
671
+ ```ts
672
+ function handle(props: ComponentProps) {
673
+ // Error: 'payload' is of type 'unknown'. Narrow it first.
674
+ props.payload.toUpperCase();
675
+
676
+ if (typeof props.payload === "string") {
677
+ props.payload.toUpperCase(); // OK after narrowing
678
+ }
679
+ }
680
+ ```
681
+
573
682
  ### `@default`
574
683
 
575
684
  By default, `sveld` infers the `@default` value from the prop's initializer and includes it in the generated TypeScript definitions:
@@ -587,9 +696,9 @@ By default, `sveld` infers the `@default` value from the prop's initializer and
587
696
  open?: boolean;
588
697
  ```
589
698
 
590
- Use the `@default` tag to explicitly document the default value. When an explicit `@default` annotation is provided, `sveld` uses it instead of the inferred value, avoiding duplicate `@default` tags in the output.
699
+ Use `@default` to document the default value. When you supply `@default`, `sveld` uses it instead of the inferred value and avoids duplicate `@default` tags in the output.
591
700
 
592
- This is useful when the initializer references a variable or expression that is not meaningful to consumers:
701
+ Use `@default` when the initializer references a variable or expression that means nothing to consumers:
593
702
 
594
703
  ```svelte
595
704
  <script>
@@ -612,7 +721,7 @@ shouldFilter?: (item: string, value: string) => boolean;
612
721
 
613
722
  #### Identifier resolution
614
723
 
615
- When a prop's initializer is a variable reference, `sveld` resolves it to the actual value automatically:
724
+ When a prop's initializer is a variable reference, `sveld` resolves it to the actual value:
616
725
 
617
726
  ```svelte
618
727
  <script>
@@ -654,7 +763,7 @@ When an explicit `@default` annotation is provided, it always takes precedence o
654
763
 
655
764
  ### `@typedef`
656
765
 
657
- The `@typedef` tag can be used to define a common type that is used multiple times within a component. All typedefs defined in a component will be exported from the generated TypeScript definition file.
766
+ The `@typedef` tag defines a shared type used multiple times in a component. All typedefs in a component are exported from the generated `.d.ts`.
658
767
 
659
768
  **Signature:**
660
769
 
@@ -703,7 +812,7 @@ The `@typedef` tag can be used to define a common type that is used multiple tim
703
812
 
704
813
  #### Using `@property` for complex typedefs
705
814
 
706
- For complex object types, use the `@property` tag to document individual properties. This provides better documentation and IDE support with per-property tooltips.
815
+ For complex object types, use `@property` to document individual fields. That gives per-property tooltips in the IDE.
707
816
 
708
817
  **Signature:**
709
818
 
@@ -774,7 +883,7 @@ export type ComponentProps = {
774
883
 
775
884
  #### Optional properties and default values
776
885
 
777
- Following JSDoc standards, use square brackets to mark properties as optional. You can also specify default values using the `[propertyName=defaultValue]` syntax.
886
+ Use square brackets for optional properties, per JSDoc. Default values use `[propertyName=defaultValue]`.
778
887
 
779
888
  **Signature:**
780
889
 
@@ -847,7 +956,7 @@ export type ComponentProps = {
847
956
  };
848
957
  ```
849
958
 
850
- > **Note:** The inline syntax `@typedef {{ name: string }} User` continues to work for backwards compatibility.
959
+ > The inline syntax `@typedef {{ name: string }} User` still works for backwards compatibility.
851
960
 
852
961
  #### Discriminated unions
853
962
 
@@ -913,7 +1022,7 @@ The same pattern works inline via `@type`, which is useful when the union is onl
913
1022
  export let result = { kind: "success", value: "ok" };
914
1023
  ```
915
1024
 
916
- In `<script lang="ts">` components, write the type alias directly `sveld` preserves it in the emitted `.d.ts`:
1025
+ In `<script lang="ts">` components, write the type alias directly. `sveld` preserves it in the emitted `.d.ts`:
917
1026
 
918
1027
  ```svelte
919
1028
  <script lang="ts">
@@ -923,11 +1032,182 @@ In `<script lang="ts">` components, write the type alias directly — `sveld` pr
923
1032
  </script>
924
1033
  ```
925
1034
 
1035
+ #### Branded types
1036
+
1037
+ A branded type is a primitive plus a unique marker so values like `UserId` are not interchangeable with any other `string`. At runtime it is still the underlying primitive; TypeScript treats the brand as a separate type. Declare the brand inline with `@type`. `sveld` copies the intersection verbatim into the emitted prop type, so the brand shows up in IntelliSense, autocomplete, and hover tooltips on the consumer side.
1038
+
1039
+ **Example:**
1040
+
1041
+ ```svelte
1042
+ <script>
1043
+ /**
1044
+ * A branded string. At runtime it is a plain `string`, but the brand makes it
1045
+ * a distinct domain type that other strings cannot be assigned to.
1046
+ *
1047
+ * @type {string & { readonly __brand: "UserId" }}
1048
+ */
1049
+ export let userId;
1050
+
1051
+ /**
1052
+ * A branded number representing a monetary amount in cents.
1053
+ *
1054
+ * @type {number & { readonly __brand: "Cents" }}
1055
+ */
1056
+ export let amount;
1057
+ </script>
1058
+ ```
1059
+
1060
+ Output:
1061
+
1062
+ ```ts
1063
+ export type ComponentProps = {
1064
+ /**
1065
+ * A branded string. At runtime it is a plain `string`, but the brand makes it
1066
+ * a distinct domain type that other strings cannot be assigned to.
1067
+ * @default undefined
1068
+ */
1069
+ userId: string & { readonly __brand: "UserId" };
1070
+
1071
+ /**
1072
+ * A branded number representing a monetary amount in cents.
1073
+ * @default undefined
1074
+ */
1075
+ amount: number & { readonly __brand: "Cents" };
1076
+ };
1077
+ ```
1078
+
1079
+ Consumers construct branded values with a narrowing cast, then get compile-time protection against mixing them up:
1080
+
1081
+ ```ts
1082
+ const userId = "user_123" as ComponentProps["userId"];
1083
+
1084
+ // Error: a plain string is not assignable to the branded userId
1085
+ component.$set({ userId: "user_123" });
1086
+ ```
1087
+
1088
+ #### Utility types
1089
+
1090
+ `sveld` preserves TypeScript utility types verbatim, so a prop type can be derived from an existing `@typedef` instead of restating its fields. `Pick`, `Omit`, `Partial`, `Required`, `Readonly`, `ReturnType`, `Parameters`, and `Awaited` pass through unchanged. When the base type changes, derived props follow.
1091
+
1092
+ **Example:**
1093
+
1094
+ ```svelte
1095
+ <script>
1096
+ /**
1097
+ * @typedef {{ id: string; size: "sm" | "md" | "lg"; disabled: boolean }} Options
1098
+ */
1099
+
1100
+ /**
1101
+ * @typedef {() => Options} Factory
1102
+ */
1103
+
1104
+ /**
1105
+ * A subset of `Options`.
1106
+ * @type {Pick<Options, "id" | "size">}
1107
+ */
1108
+ export let summary;
1109
+
1110
+ /**
1111
+ * Everything in `Options` except `disabled`.
1112
+ * @type {Omit<Options, "disabled">}
1113
+ */
1114
+ export let editable;
1115
+
1116
+ /**
1117
+ * Derived from the factory's return type rather than restated.
1118
+ * @type {ReturnType<Factory>}
1119
+ */
1120
+ export let defaults;
1121
+
1122
+ /**
1123
+ * The resolved value of an async source.
1124
+ * @type {Awaited<Promise<Options>>}
1125
+ */
1126
+ export let resolved;
1127
+ </script>
1128
+ ```
1129
+
1130
+ Output:
1131
+
1132
+ ```ts
1133
+ export interface Options {
1134
+ id: string;
1135
+ size: "sm" | "md" | "lg";
1136
+ disabled: boolean;
1137
+ }
1138
+
1139
+ export type Factory = () => Options;
1140
+
1141
+ export type ComponentProps = {
1142
+ summary: Pick<Options, "id" | "size">;
1143
+ editable: Omit<Options, "disabled">;
1144
+ defaults: ReturnType<Factory>;
1145
+ resolved: Awaited<Promise<Options>>;
1146
+ };
1147
+ ```
1148
+
1149
+ #### Type guards
1150
+
1151
+ A prop typed as a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) (`value is T`) lets a component accept a user-defined type guard. `sveld` copies the predicate verbatim, whether you write it inline with `@type` or name it with `@typedef`, so narrowing survives in the generated `.d.ts`. Name guards `isX` or `hasX`, and make sure the implementation actually checks what the predicate claims.
1152
+
1153
+ **Example:**
1154
+
1155
+ ```svelte
1156
+ <script>
1157
+ /**
1158
+ * @typedef {{ id: string; name: string }} User
1159
+ */
1160
+
1161
+ /**
1162
+ * A type guard. It accepts an `unknown` value and returns a type predicate,
1163
+ * so callers can narrow `unknown` to `User` before accessing its fields.
1164
+ *
1165
+ * @type {(value: unknown) => value is User}
1166
+ */
1167
+ export let isUser;
1168
+
1169
+ /**
1170
+ * A type guard expressed as a reusable `@typedef`.
1171
+ *
1172
+ * @typedef {(value: unknown) => value is User} UserGuard
1173
+ */
1174
+
1175
+ /** @type {UserGuard} */
1176
+ export let validate;
1177
+ </script>
1178
+ ```
1179
+
1180
+ Output:
1181
+
1182
+ ```ts
1183
+ export interface User {
1184
+ id: string;
1185
+ name: string;
1186
+ }
1187
+
1188
+ export type UserGuard = (value: unknown) => value is User;
1189
+
1190
+ export type ComponentProps = {
1191
+ isUser: (value: unknown) => value is User;
1192
+ validate: UserGuard;
1193
+ };
1194
+ ```
1195
+
1196
+ Consumers use the guard to narrow an `unknown` value:
1197
+
1198
+ ```ts
1199
+ function render(value: unknown, props: ComponentProps) {
1200
+ if (props.isUser(value)) {
1201
+ value.name; // narrowed to User
1202
+ }
1203
+ }
1204
+ ```
1205
+
926
1206
  ### `@callback`
927
1207
 
928
- The `@callback` tag defines a function type using `@param` and `@returns` tags, following the [TypeScript JSDoc `@callback` specification](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#callback). Like `@typedef`, callbacks are exported from the generated TypeScript definition file.
1208
+ The `@callback` tag defines a function type with `@param` and `@returns`, following the [TypeScript JSDoc `@callback` spec](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#callback). Like `@typedef`, callbacks are exported from the generated `.d.ts`.
929
1209
 
930
- This is useful for typing callback props without using inline function type syntax.
1210
+ Use it for callback props when you do not want inline function type syntax.
931
1211
 
932
1212
  **Signature:**
933
1213
 
@@ -1009,9 +1289,9 @@ When `@returns` is omitted, the return type defaults to `void`. When no `@param`
1009
1289
 
1010
1290
  ### `@slot` / `@snippet`
1011
1291
 
1012
- Use the `@slot` tag for typing component slots. For Svelte 5 runes components, `@snippet` is also supported as an alias. Both are non-standard JSDoc tags.
1292
+ Use `@slot` to type component slots. In Svelte 5 runes components, `@snippet` is an alias. Both are non-standard JSDoc tags.
1013
1293
 
1014
- Descriptions are optional for every slot, including the default slot: use prose lines in the same `/** */` block above `@slot` / `@snippet`, and/or an inline description on the `@slot` line for named slots.
1294
+ Descriptions are optional for every slot, including the default slot. Put prose in the same `/** */` block above `@slot` / `@snippet`, or an inline description on the `@slot` line for named slots.
1015
1295
 
1016
1296
  **Signature:**
1017
1297
 
@@ -1079,9 +1359,9 @@ Omit the `slot-name` to type the default slot.
1079
1359
 
1080
1360
  #### Extra JSDoc tags before `@slot`
1081
1361
 
1082
- Tags such as `@example`, `@deprecated`, `@see`, or `@since` that appear **after** the prose description and **before** the `@slot` / `@snippet` line are carried into generated `.d.ts` files: the emitted JSDoc above each slots snippet prop (and the traditional `SlotDefs` shape) contains the description, then those tags in source order. The same entries are available on each slot in JSON output as `tags: [{ "name", "body" }, ...]`.
1362
+ Tags such as `@example`, `@deprecated`, `@see`, or `@since` that appear after the prose description and before the `@slot` / `@snippet` line are copied into generated `.d.ts` files. The emitted JSDoc above each slot's snippet prop (and the traditional `SlotDefs` shape) lists the description, then those tags in source order. The same entries appear in JSON as `tags: [{ "name", "body" }, ...]`.
1083
1363
 
1084
- Put `@slot` / `@snippet` last in the block (`description` optional extra tags slot tag). Tags placed after `@slot` / `@snippet` in the same comment are not tied to that slot. Unknown tag names are passed through as-is (no allowlist). Markdown docs do not render slot descriptions or these tags yet; use TypeScript hover or JSON for that metadata.
1364
+ Put `@slot` / `@snippet` last in the block (description, optional extra tags, slot tag). Tags after `@slot` / `@snippet` in the same comment are not tied to that slot. Unknown tag names pass through as-is. Markdown docs do not render slot descriptions or these tags yet; use TypeScript hover or JSON.
1085
1365
 
1086
1366
  **Example (default slot with `@example` and `@deprecated`):**
1087
1367
 
@@ -1105,11 +1385,11 @@ Put `@slot` / `@snippet` last in the block (`description` → optional extra tag
1105
1385
 
1106
1386
  #### Svelte 5 Snippet Compatibility
1107
1387
 
1108
- For Svelte 5 compatibility, `sveld` automatically generates optional snippet props for all slots. This allows consumers to use either the traditional slot syntax or Svelte 5's `{#snippet}` syntax.
1388
+ For Svelte 5, `sveld` generates optional snippet props for all slots so consumers can use traditional slot syntax or `{#snippet}`.
1109
1389
 
1110
- When parsing runes components, `sveld` maps `{@render ...}` calls back into the same slot metadata used for traditional `<slot>` declarations. Reserved snippet props such as `children`, along with named snippet props discovered from `{@render ...}`, are represented through `slots` metadata and generated snippet prop types rather than duplicated in the `props` output.
1390
+ When parsing runes components, `sveld` maps `{@render ...}` calls back into the same slot metadata used for `<slot>`. Reserved snippet props like `children`, plus named snippet props from `{@render ...}`, live in `slots` metadata and generated snippet prop types, not duplicated in `props`.
1111
1391
 
1112
- Positional snippet calls such as `{@render row?.(item, index)}` are preserved as typed props when the prop itself has an explicit type like `Snippet<[Item, number]>`. They are not converted into synthetic slot metadata.
1392
+ Positional snippet calls like `{@render row?.(item, index)}` stay typed props when the prop has an explicit type like `Snippet<[Item, number]>`. They are not turned into synthetic slot metadata.
1113
1393
 
1114
1394
  For slots with props (e.g., `let:prop`), the generated type uses a Snippet-compatible signature:
1115
1395
 
@@ -1125,8 +1405,8 @@ slotName?: (this: void) => void;
1125
1405
 
1126
1406
  **Why this signature?**
1127
1407
 
1128
- - **`this: void`** Ensures the snippet cannot be called with a `this` context, matching Svelte's internal enforcement that snippets are pure render functions
1129
- - **`...args: [Props]`** Uses tuple spread for type-safe parameters. This accepts fixed-length tuples (like `[{ row: Row }]`) while rejecting array types (like `Props[]`), matching how Svelte's `Snippet<T>` type works
1408
+ - **`this: void`** blocks calling the snippet with a `this` context, matching Svelte's rule that snippets are pure render functions
1409
+ - **`...args: [Props]`** uses tuple spread for type-safe parameters. It accepts fixed-length tuples (like `[{ row: Row }]`) and rejects array types (like `Props[]`), matching Svelte's `Snippet<T>` type
1130
1410
 
1131
1411
  **Default slot (`children` prop):**
1132
1412
 
@@ -1168,7 +1448,7 @@ type DropdownProps = {
1168
1448
  </DataTable>
1169
1449
  ```
1170
1450
 
1171
- The generated TypeScript definition includes both the snippet prop and the traditional slot definition:
1451
+ Generated output includes both the snippet prop and the traditional slot definition:
1172
1452
 
1173
1453
  ```ts
1174
1454
  type DataTableProps<Row> = {
@@ -1213,7 +1493,7 @@ export default class DataTable<Row> extends SvelteComponentTyped<
1213
1493
 
1214
1494
  Use the `@event` tag to type dispatched events. An event name is required and a description optional.
1215
1495
 
1216
- In Svelte 5 runes components, callback props such as `onclick` are treated as component props, not events. The `events` output remains reserved for real dispatched events and legacy forwarded events. If a runes component documents `@event foo` and exposes a matching callback prop like `onfoo` without actually dispatching or forwarding `foo`, `sveld` aliases that documentation onto the callback prop instead of synthesizing an emitted event.
1496
+ In Svelte 5 runes components, callback props like `onclick` are props, not events. The `events` output stays reserved for dispatched events and legacy forwarded events. If a runes component documents `@event foo` and exposes a matching callback prop like `onfoo` without actually dispatching or forwarding `foo`, `sveld` aliases that documentation onto the callback prop instead of synthesizing an emitted event.
1217
1497
 
1218
1498
  Use `null` as the value if no event detail is provided.
1219
1499
 
@@ -1299,7 +1579,7 @@ export default class Component extends SvelteComponentTyped<
1299
1579
 
1300
1580
  #### Using `@property` for complex event details
1301
1581
 
1302
- For events with complex object payloads, use the `@property` tag to document individual properties. The main comment description will be used as the event description.
1582
+ For events with complex object payloads, use `@property` to document individual fields. The main comment becomes the event description.
1303
1583
 
1304
1584
  **Signature:**
1305
1585
 
@@ -1394,7 +1674,7 @@ export default class Component extends SvelteComponentTyped<
1394
1674
 
1395
1675
  #### Optional properties in event details
1396
1676
 
1397
- Just like with typedefs, you can mark event detail properties as optional using square brackets. This is useful when some properties may not always be included in the event payload.
1677
+ Like typedefs, you can mark event detail properties as optional with square brackets when they are not always in the payload.
1398
1678
 
1399
1679
  **Example:**
1400
1680
 
@@ -1484,18 +1764,53 @@ export default class Component extends SvelteComponentTyped<
1484
1764
  > {}
1485
1765
  ```
1486
1766
 
1767
+ #### Discriminated unions in event details
1768
+
1769
+ When the event detail is a union (or any non-object shape), use `@type` to declare it directly. An explicit `@type` wins over `@property` tags, so the union is copied verbatim into the emitted `.d.ts` instead of being flattened into independent property unions. The only exception is `@type {object}`, which tells `sveld` to build the shape from `@property` tags (as shown above).
1770
+
1771
+ **Example:**
1772
+
1773
+ ```svelte
1774
+ <script>
1775
+ /**
1776
+ * @event sort
1777
+ * @type {{ key: null; direction: "none" } | { key: string; direction: "ascending" | "descending" }}
1778
+ * Dispatched when a sortable column header would change the active sort.
1779
+ */
1780
+
1781
+ import { createEventDispatcher } from "svelte";
1782
+
1783
+ const dispatch = createEventDispatcher();
1784
+ </script>
1785
+ ```
1786
+
1787
+ Output:
1788
+
1789
+ ```ts
1790
+ export default class Component extends SvelteComponentTyped<
1791
+ ComponentProps,
1792
+ {
1793
+ /** Dispatched when a sortable column header would change the active sort. */
1794
+ sort: CustomEvent<{ key: null; direction: "none" } | { key: string; direction: "ascending" | "descending" }>;
1795
+ },
1796
+ Record<string, never>
1797
+ > {}
1798
+ ```
1799
+
1800
+ Any free-text prose after the tags is attached to the event description, not to a property doc.
1801
+
1487
1802
  ### Context API
1488
1803
 
1489
- `sveld` automatically generates TypeScript definitions for Svelte's `setContext`/`getContext` API by extracting types from JSDoc annotations on the context values.
1804
+ `sveld` generates TypeScript definitions for Svelte's `setContext`/`getContext` by extracting types from JSDoc on context values.
1490
1805
 
1491
1806
  #### How it works
1492
1807
 
1493
- When you use `setContext` in a component, `sveld` will:
1808
+ When you call `setContext` in a component, `sveld`:
1494
1809
 
1495
- 1. Detect the `setContext` call
1496
- 2. Extract the context key (must be a string literal)
1497
- 3. Find JSDoc `@type` annotations on the variables being passed
1498
- 4. Generate a TypeScript type export for the context
1810
+ 1. Detects the `setContext` call
1811
+ 2. Extracts the context key (must be a string literal)
1812
+ 3. Finds JSDoc `@type` annotations on the variables being passed
1813
+ 4. Generates a TypeScript type export for the context
1499
1814
 
1500
1815
  #### Example
1501
1816
 
@@ -1589,7 +1904,6 @@ export default class Modal extends SvelteComponentTyped<
1589
1904
  import { getContext } from 'svelte';
1590
1905
  import type { SimpleModalContext } from 'modal-library/Modal.svelte';
1591
1906
 
1592
- // Fully typed with autocomplete!
1593
1907
  const { close, open } = getContext<SimpleModalContext>('simple-modal');
1594
1908
  </script>
1595
1909
 
@@ -1598,7 +1912,7 @@ export default class Modal extends SvelteComponentTyped<
1598
1912
 
1599
1913
  #### Explicitly typing contexts
1600
1914
 
1601
- There are several ways to provide type information for contexts:
1915
+ There are several ways to type contexts:
1602
1916
 
1603
1917
  **Option 1: Inline JSDoc on variables (recommended)**
1604
1918
 
@@ -1669,7 +1983,7 @@ There are several ways to provide type information for contexts:
1669
1983
  </script>
1670
1984
  ```
1671
1985
 
1672
- > **Note:** For best results, use explicit JSDoc `@type` annotations. Inline functions without annotations will be inferred with generic signatures.
1986
+ > Inline functions without `@type` annotations get generic inferred signatures. Add explicit JSDoc when you care about the shape.
1673
1987
 
1674
1988
  #### Notes
1675
1989
 
@@ -1689,9 +2003,9 @@ There are several ways to provide type information for contexts:
1689
2003
 
1690
2004
  ### `@restProps`
1691
2005
 
1692
- `sveld` can pick up inline HTML elements that `$$restProps` is forwarded to. However, it cannot infer the underlying element for instantiated components.
2006
+ `sveld` can detect inline HTML elements that `$$restProps` is forwarded to. It cannot infer the underlying element for instantiated components.
1693
2007
 
1694
- You can use the `@restProps` tag to specify the element tags that `$$restProps` is forwarded to.
2008
+ Use `@restProps` to name the element tags `$$restProps` is forwarded to.
1695
2009
 
1696
2010
  **Signature:**
1697
2011
 
@@ -1745,9 +2059,9 @@ You can use the `@restProps` tag to specify the element tags that `$$restProps`
1745
2059
 
1746
2060
  ### `@extendProps`
1747
2061
 
1748
- In some cases, a component may be based on another component. The `@extendProps` tag can be used to extend generated component props.
2062
+ When a component wraps another, use `@extendProps` to extend generated props.
1749
2063
 
1750
- > **Note:** `@extends` is supported as an alias but `@extendProps` is preferred to avoid conflicts with standard JSDoc `@extends` (used for class inheritance).
2064
+ > `@extends` works as an alias, but prefer `@extendProps` to avoid clashing with standard JSDoc `@extends` for class inheritance.
1751
2065
 
1752
2066
  **Signature:**
1753
2067
 
@@ -1776,7 +2090,7 @@ Svelte supports defining generics via the [`generics` attribute](https://svelte.
1776
2090
  <script lang="ts" generics="Row extends DataTableRow = any"></script>
1777
2091
  ```
1778
2092
 
1779
- Because `sveld` is designed to support JavaScript-only usage as a baseline, the API design to specify generics uses the standard JSDoc `@template` tag. The `@generics` tag is also supported as an alias.
2093
+ Because `sveld` targets JavaScript-only usage as a baseline, generics use the standard JSDoc `@template` tag. `@generics` is also supported as an alias.
1780
2094
 
1781
2095
  **Signature:** Uses standard [JSDoc `@template` syntax](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template):
1782
2096
 
@@ -1842,7 +2156,7 @@ Because `sveld` is designed to support JavaScript-only usage as a baseline, the
1842
2156
  <slot {headers} {rows} />
1843
2157
  ```
1844
2158
 
1845
- The generated TypeScript definition will resemble the following:
2159
+ Generated output looks like this:
1846
2160
 
1847
2161
  ```ts
1848
2162
  export type ComponentProps<Row extends DataTableRow = DataTableRow> = {
@@ -1885,7 +2199,7 @@ export default class Component<
1885
2199
 
1886
2200
  ### `@generics`
1887
2201
 
1888
- As an alternative to `@template`, sveld also supports a custom `@generics` tag. Unlike `@template`, which is [officially supported by JSDoc/TypeScript](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template), `@generics` is sveld-specific. However, its syntax can be more readable since the full constraint is written inline:
2202
+ As an alternative to `@template`, sveld supports `@generics`. Unlike `@template`, which [JSDoc/TypeScript support officially](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template), `@generics` is sveld-specific. The syntax can be easier to read because the full constraint is inline:
1889
2203
 
1890
2204
  ```js
1891
2205
  /**
@@ -1967,9 +2281,9 @@ export default class Button extends SvelteComponentTyped<
1967
2281
 
1968
2282
  ### Accessor Props
1969
2283
 
1970
- Exported functions and consts become accessor props in generated TypeScript definitions. Use `@type` to document function signatures, or use `@param` and `@returns` (or `@return`) JSDoc tags for richer documentation.
2284
+ Exported functions and consts become accessor props in generated TypeScript definitions. Use `@type` for function signatures, or `@param` and `@returns` (or `@return`) for richer docs.
1971
2285
 
1972
- Note that `@type` tag annotations take precedence over `@param`/`@returns` tags.
2286
+ `@type` wins over `@param`/`@returns` when both are present.
1973
2287
 
1974
2288
  **Signature:**
1975
2289
 
@@ -2105,7 +2419,7 @@ When only `@param` tags are present without `@returns`, the return type defaults
2105
2419
 
2106
2420
  ## Contributing
2107
2421
 
2108
- Refer to the [contributing guidelines](CONTRIBUTING.md).
2422
+ See [contributing guidelines](CONTRIBUTING.md).
2109
2423
 
2110
2424
  ## License
2111
2425