@tsrx/core 0.1.57 → 0.1.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core compiler infrastructure for TSRX syntax",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.1.57",
6
+ "version": "0.1.59",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
@@ -71,19 +71,21 @@
71
71
  "dependencies": {
72
72
  "@jridgewell/sourcemap-codec": "^1.5.5",
73
73
  "@noble/hashes": "^2.2.0",
74
- "@sveltejs/acorn-typescript": "^1.0.11",
74
+ "@sveltejs/acorn-typescript": "^1.0.13",
75
75
  "@types/estree-jsx": "^1.0.5",
76
76
  "@types/estree": "^1.0.8",
77
77
  "acorn": "^8.17.0",
78
- "esrap": "^2.3.0",
78
+ "esrap": "^2.3.2",
79
79
  "is-reference": "^3.0.3",
80
80
  "magic-string": "^0.30.18",
81
- "zimmerframe": "^1.1.2"
81
+ "zimmerframe": "^1.1.2",
82
+ "@tsrx/runtime": "0.1.1"
82
83
  },
83
84
  "devDependencies": {
84
85
  "@types/node": "^24.3.0",
85
86
  "@typescript-eslint/types": "^8.40.0",
86
87
  "@volar/language-core": "~2.4.28",
88
+ "@volar/source-map": "~2.4.28",
87
89
  "typescript": "^5.9.3",
88
90
  "vite": "^8.1.5",
89
91
  "vscode-languageserver-types": "^3.17.5"
@@ -29,6 +29,14 @@ export function is_triple_slash_directive(comment) {
29
29
  return /^\/\s*<(reference|amd-module|amd-dependency)/.test(value);
30
30
  }
31
31
 
32
+ /**
33
+ * @param {AST.CommentWithLocation} comment
34
+ * @returns {boolean}
35
+ */
36
+ export function is_jsdoc_comment(comment) {
37
+ return comment.type === 'Block' && comment.value.startsWith('*');
38
+ }
39
+
32
40
  /**
33
41
  * Check if a comment is a JSDoc comment with TypeScript annotations
34
42
  * Examples: block comments containing `@type`, `@typedef`, `@param`, `@returns`, etc.
@@ -36,10 +44,7 @@ export function is_triple_slash_directive(comment) {
36
44
  * @returns {boolean}
37
45
  */
38
46
  export function is_jsdoc_ts_annotation(comment) {
39
- if (comment.type !== 'Block') return false;
40
-
41
- // JSDoc comments start with /** which means the value starts with * after /* is stripped
42
- if (!comment.value.startsWith('*')) return false;
47
+ if (!is_jsdoc_comment(comment)) return false;
43
48
 
44
49
  // Check if it contains TypeScript-relevant tags
45
50
  const tsAnnotations = [
@@ -84,6 +89,36 @@ export function should_preserve_comment(comment) {
84
89
  );
85
90
  }
86
91
 
92
+ /**
93
+ * Declaration generators also consume documentation and custom `// @...`
94
+ * annotations from JSX-backed virtual TypeScript. Keep this broader policy
95
+ * separate from the legacy to_ts printer's semantic-comment policy.
96
+ * @param {AST.CommentWithLocation} comment
97
+ * @returns {boolean}
98
+ */
99
+ export function should_preserve_jsx_tooling_comment(comment) {
100
+ return (
101
+ should_preserve_comment(comment) ||
102
+ is_jsdoc_comment(comment) ||
103
+ (comment.type === 'Line' && comment.value.trimStart().startsWith('@'))
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Only file-wide directives may move ahead of generated imports or hoists.
109
+ * In particular, @ts-ignore/@ts-expect-error and declaration JSDoc must stay
110
+ * with the statement they describe.
111
+ * @param {AST.CommentWithLocation} comment
112
+ * @returns {boolean}
113
+ */
114
+ export function is_file_level_pragma(comment) {
115
+ return (
116
+ is_jsx_pragma(comment) ||
117
+ is_triple_slash_directive(comment) ||
118
+ (comment.type === 'Line' && /^\s*@ts-(?:no)?check\b/.test(comment.value))
119
+ );
120
+ }
121
+
87
122
  /**
88
123
  * Format a comment for output
89
124
  * @param {AST.CommentWithLocation} comment
@@ -1,112 +1 @@
1
- /**
2
- * @template T
3
- * @template U
4
- * @param {Iterable<T> | Iterator<T>} iterable
5
- * @param {(item: T, index: number, is_last: boolean) => U} fn
6
- * @param {() => U | U[]} [tail]
7
- * @param {() => U | U[]} [empty]
8
- * @returns {U[]}
9
- */
10
- export function map_iterable(iterable, fn, tail, empty) {
11
- if (Array.isArray(iterable)) {
12
- return map_array(iterable, fn, tail, empty);
13
- }
14
-
15
- /** @type {Iterator<T>} */
16
- var iterator;
17
- var iterable_prop = /** @type {Iterable<T>} */ (iterable)[Symbol.iterator];
18
-
19
- if (typeof iterable_prop === 'function') {
20
- iterator = iterable_prop.call(iterable);
21
- } else if (typeof (/** @type {Iterator<T>} */ (iterable).next) === 'function') {
22
- iterator = Iterator.from(iterable);
23
- } else {
24
- throw new TypeError('The loop target has to be an Iterable');
25
- }
26
-
27
- var current = iterator.next();
28
- if (current.done) {
29
- if (!empty) {
30
- return [];
31
- }
32
- var empty_value = empty();
33
- if (Array.isArray(empty_value)) {
34
- return empty_value;
35
- }
36
- return [empty_value];
37
- }
38
-
39
- var index = 0;
40
- var result = [];
41
- while (true) {
42
- var next = iterator.next();
43
- var value = fn(current.value, index++, !!next.done);
44
- if (Array.isArray(value)) {
45
- for (var j = 0; j < value.length; j++) {
46
- result.push(value[j]);
47
- }
48
- } else {
49
- result.push(value);
50
- }
51
- if (next.done) {
52
- break;
53
- }
54
- current = next;
55
- }
56
- if (tail) {
57
- var tail_value = tail();
58
- if (Array.isArray(tail_value)) {
59
- for (var j = 0; j < tail_value.length; j++) {
60
- result.push(tail_value[j]);
61
- }
62
- } else {
63
- result.push(tail_value);
64
- }
65
- }
66
- return result;
67
- }
68
-
69
- /**
70
- * @template T
71
- * @template U
72
- * @param {Array<T>} array
73
- * @param {(item: T, index: number, is_last: boolean) => U} fn
74
- * @param {() => U | U[]} [tail]
75
- * @param {() => U | U[]} [empty]
76
- * @returns {U[]}
77
- */
78
- function map_array(array, fn, tail, empty) {
79
- var length = array.length;
80
- if (length === 0) {
81
- if (!empty) {
82
- return [];
83
- }
84
- var empty_value = empty();
85
- if (Array.isArray(empty_value)) {
86
- return empty_value;
87
- }
88
- return [empty_value];
89
- }
90
- var result = [];
91
- for (var i = 0; i < length; i++) {
92
- var value = fn(array[i], i, i === length - 1);
93
- if (Array.isArray(value)) {
94
- for (var j = 0; j < value.length; j++) {
95
- result.push(value[j]);
96
- }
97
- } else {
98
- result.push(value);
99
- }
100
- }
101
- if (tail) {
102
- var tail_value = tail();
103
- if (Array.isArray(tail_value)) {
104
- for (var j = 0; j < tail_value.length; j++) {
105
- result.push(tail_value[j]);
106
- }
107
- } else {
108
- result.push(tail_value);
109
- }
110
- }
111
- return result;
112
- }
1
+ export * from '@tsrx/runtime/iterable';
@@ -1,139 +1 @@
1
- /** @type {typeof Object.getOwnPropertyDescriptor} */
2
- export var get_descriptor = Object.getOwnPropertyDescriptor;
3
- /** @type {typeof Object.getOwnPropertyDescriptors} */
4
- export var get_descriptors = Object.getOwnPropertyDescriptors;
5
- /** @type {typeof Array.from} */
6
- export var array_from = Array.from;
7
- /** @type {typeof Array.isArray} */
8
- export var is_array = Array.isArray;
9
- /** @type {typeof Object.defineProperty} */
10
- export var define_property = Object.defineProperty;
11
- /** @type {typeof Object.getPrototypeOf} */
12
- export var get_prototype_of = Object.getPrototypeOf;
13
- /** @type {typeof Object.values} */
14
- export var object_values = Object.values;
15
- /** @type {typeof Object.entries} */
16
- export var object_entries = Object.entries;
17
- /** @type {typeof Object.keys} */
18
- export var object_keys = Object.keys;
19
- /** @type {typeof Object.getOwnPropertySymbols} */
20
- export var get_own_property_symbols = Object.getOwnPropertySymbols;
21
- /** @type {typeof structuredClone} */
22
- export var structured_clone = structuredClone;
23
- /** @type {typeof Object.prototype} */
24
- export var object_prototype = Object.prototype;
25
- /** @type {typeof Array.prototype} */
26
- export var array_prototype = Array.prototype;
27
- /** @type {typeof Object.prototype.hasOwnProperty} */
28
- export var has_own_property = object_prototype.hasOwnProperty;
29
-
30
- /**
31
- * @param {object} value
32
- * @param {PropertyKey} key
33
- * @returns {boolean}
34
- */
35
- export function has_prototype_accessor(value, key) {
36
- var proto = get_prototype_of(value);
37
- while (proto != null) {
38
- var descriptor = get_descriptor(proto, key);
39
- if (descriptor !== undefined) {
40
- return typeof descriptor.get === 'function' || typeof descriptor.set === 'function';
41
- }
42
- proto = get_prototype_of(proto);
43
- }
44
- return false;
45
- }
46
-
47
- /**
48
- * Slice helper for arrays and array-like values.
49
- * @template T
50
- * @param {ArrayLike<T>} array_like
51
- * @param {...number} args
52
- * @returns {T[]}
53
- */
54
- export function array_slice(array_like, ...args) {
55
- return is_array(array_like)
56
- ? array_like.slice(...args)
57
- : array_prototype.slice.call(array_like, ...args);
58
- }
59
-
60
- /**
61
- * Converts iterables, iterators, and array-like values to an array from an index.
62
- * @template T
63
- * @param {Iterable<T> | Iterator<T> | ArrayLike<T>} iterable
64
- * @param {number} [index]
65
- * @returns {T[]}
66
- */
67
- export function iterable_array_from(iterable, index = 0) {
68
- /** @type {Iterator<T>} */
69
- var iterator;
70
- var iterable_prop = /** @type {Iterable<T>} */ (iterable)[Symbol.iterator];
71
-
72
- if (typeof iterable_prop === 'function') {
73
- iterator = iterable_prop.call(iterable);
74
- } else if (typeof (/** @type {Iterator<T>} */ (iterable).next) === 'function') {
75
- iterator = Iterator.from(/** @type {Iterator<T>} */ (iterable));
76
- } else {
77
- return array_from(/** @type {ArrayLike<T>} */ (iterable)).slice(index);
78
- }
79
-
80
- var result = [];
81
- var i = 0;
82
- var current = iterator.next();
83
- while (!current.done) {
84
- if (i++ < index) {
85
- current = iterator.next();
86
- continue;
87
- }
88
- result.push(current.value);
89
- current = iterator.next();
90
- }
91
- return result;
92
- }
93
-
94
- /**
95
- * Creates a shallow forwarding object without one prop. Values are exposed through
96
- * getters so compiler-emitted reactive prop accessors are not snapshotted.
97
- *
98
- * @template {object} [T=Record<PropertyKey, unknown>]
99
- * @template {PropertyKey} [K=PropertyKey]
100
- * @param {T | null | undefined} props
101
- * @param {K} exclude_prop
102
- * @returns {Omit<T, K>} the forwarding object; `{}` when `props` is nullish
103
- */
104
- export function exclude_prop_from_object(props, exclude_prop) {
105
- /** @type {Record<PropertyKey, unknown>} */
106
- const next = {};
107
-
108
- if (props != null) {
109
- const source = /** @type {Record<PropertyKey, unknown>} */ (props);
110
-
111
- for (const prop of Reflect.ownKeys(source)) {
112
- if (prop === exclude_prop) continue;
113
-
114
- const descriptor = get_descriptor(source, prop);
115
- if (!descriptor?.enumerable) continue;
116
-
117
- /** @type {PropertyDescriptor} */
118
- const forwarding_descriptor = {
119
- enumerable: true,
120
- configurable: true,
121
- get() {
122
- return source[prop];
123
- },
124
- };
125
-
126
- if (descriptor.writable === true || typeof descriptor.set === 'function') {
127
- forwarding_descriptor.set = (value) => {
128
- source[prop] = value;
129
- };
130
- }
131
-
132
- define_property(next, prop, forwarding_descriptor);
133
- }
134
- }
135
-
136
- // The forwarding object is assembled key by key, which no incremental type
137
- // can describe; it mirrors `props` minus `exclude_prop` by construction.
138
- return /** @type {Omit<T, K>} */ (next);
139
- }
1
+ export * from '@tsrx/runtime/language-helpers';
@@ -1,319 +1 @@
1
- /** @import { MergeableRef, RefProp, RefValue, SpreadProps } from '../../types/runtime/ref' */
2
-
3
- import {
4
- has_own_property,
5
- get_descriptor,
6
- has_prototype_accessor,
7
- is_array,
8
- } from '@tsrx/core/runtime/language-helpers';
9
-
10
- const REF_VALUE = Symbol();
11
-
12
- /**
13
- * Merge multiple refs (function refs and ref objects) into a single
14
- * callback ref. Used by React, Preact, and Vue targets when an element has
15
- * more than one `ref` attribute.
16
- * This is a public method and also used by the compiler to unite any refs with
17
- * any of the supported syntaxes. It does not process spreads, that is delegated to
18
- * `normalize_spread_props`.
19
- *
20
- * @template [T=Element]
21
- * @param {...MergeableRef<T>} refs
22
- * @returns {(node: T | null) => (() => void)}
23
- */
24
- export function mergeRefs(...refs) {
25
- return (node) => {
26
- /** @type {Array<() => void>} */
27
- const cleanups = [];
28
- for (const ref of refs) {
29
- if (ref == null) continue;
30
- if (typeof ref === 'function') {
31
- const result = ref(node);
32
- if (typeof result === 'function') {
33
- cleanups.push(result);
34
- } else {
35
- cleanups.push(() => ref(null));
36
- }
37
- } else if (is_ref_object(ref, 'current')) {
38
- ref.current = node;
39
- cleanups.push(() => {
40
- ref.current = null;
41
- });
42
- } else if (is_ref_object(ref, 'value')) {
43
- ref.value = node;
44
- cleanups.push(() => {
45
- ref.value = null;
46
- });
47
- }
48
- }
49
- return () => {
50
- for (const cleanup of cleanups) cleanup();
51
- };
52
- };
53
- }
54
-
55
- export { is_ref_prop as isRefProp };
56
-
57
- /**
58
- * A ref value that is a function is a callback ref — the bare-element branch of
59
- * `RefValue` is never callable.
60
- *
61
- * @template T
62
- * @param {RefValue<T>} value
63
- * @returns {value is (node: T | null) => void | (() => void)}
64
- */
65
- function is_ref_callback(value) {
66
- return typeof value === 'function';
67
- }
68
-
69
- /**
70
- * @param {unknown} value
71
- * @returns {value is RefProp<Element>}
72
- */
73
- function is_ref_prop(value) {
74
- return typeof value === 'function' && REF_VALUE in value;
75
- }
76
-
77
- /**
78
- * @template [T=Element]
79
- * @param {RefValue<T>} ref_value
80
- * @param {T | null} node
81
- * @param {(value: T | null) => void} [set_ref_value]
82
- * @returns {void | (() => void)}
83
- */
84
- export function apply_ref_value(ref_value, node, set_ref_value) {
85
- if (is_array(ref_value)) {
86
- /** @type {Array<() => void>} */
87
- const cleanups = [];
88
- for (const item of ref_value) {
89
- const cleanup = apply_ref_value(item, node);
90
- if (typeof cleanup === 'function') {
91
- cleanups.push(cleanup);
92
- } else if (is_ref_callback(item) && node !== null) {
93
- cleanups.push(() => item(null));
94
- }
95
- }
96
- if (cleanups.length > 0) {
97
- return () => {
98
- for (const cleanup of cleanups) cleanup();
99
- };
100
- }
101
- return;
102
- }
103
-
104
- if (is_ref_callback(ref_value)) {
105
- return ref_value(node);
106
- }
107
-
108
- if (ref_value && typeof ref_value === 'object') {
109
- if (is_ref_object(ref_value, 'current')) {
110
- ref_value.current = node;
111
- return () => {
112
- ref_value.current = null;
113
- };
114
- }
115
-
116
- if (is_ref_object(ref_value, 'value')) {
117
- ref_value.value = node;
118
- return () => {
119
- ref_value.value = null;
120
- };
121
- }
122
- }
123
-
124
- if (set_ref_value !== undefined) {
125
- set_ref_value(node);
126
- }
127
- }
128
-
129
- /**
130
- * @template [T=Element]
131
- * @param {() => RefValue<T>} get_ref_value
132
- * @param {(value: T | null) => void} [set_ref_value]
133
- * @returns {RefProp<T>}
134
- */
135
- export function create_ref_prop(get_ref_value, set_ref_value) {
136
- /**
137
- * @param {T | null} node
138
- * @returns {void | (() => void)}
139
- */
140
- function ref_prop_callback(node) {
141
- const ref_value = get_ref_value();
142
- const cleanup = apply_ref_value(ref_value, node, set_ref_value);
143
- if (typeof cleanup === 'function' || node === null) {
144
- return cleanup;
145
- }
146
- return () => {
147
- apply_ref_value(ref_value, null, set_ref_value);
148
- };
149
- }
150
-
151
- Object.defineProperty(ref_prop_callback, REF_VALUE, {
152
- value: 'ref_value',
153
- enumerable: false,
154
- });
155
-
156
- return ref_prop_callback;
157
- }
158
-
159
- /**
160
- * @template [T=Element]
161
- * @param {...RefValue<T>} refs
162
- * @returns {RefValue<T>} the single surviving ref, or a callback applying all
163
- */
164
- export function merge_ref_props(...refs) {
165
- const filtered = refs.filter((ref) => ref != null);
166
-
167
- if (filtered.length === 0) {
168
- return undefined;
169
- }
170
-
171
- if (filtered.length === 1) {
172
- return filtered[0];
173
- }
174
-
175
- /**
176
- * @param {T | null} node
177
- * @returns {void | (() => void)}
178
- */
179
- function merged_ref_prop(node) {
180
- /** @type {Array<() => void>} */
181
- const cleanups = [];
182
-
183
- for (const ref of filtered) {
184
- const cleanup = apply_ref_value(ref, node);
185
- if (typeof cleanup === 'function') {
186
- cleanups.push(cleanup);
187
- } else if (is_ref_callback(ref) && node !== null) {
188
- cleanups.push(() => ref(null));
189
- }
190
- }
191
-
192
- return () => {
193
- for (const cleanup of cleanups) {
194
- cleanup();
195
- }
196
- };
197
- }
198
-
199
- return merged_ref_prop;
200
- }
201
-
202
- /**
203
- * @param {object | null | undefined} props a props bag; `object` rather than an
204
- * index signature so an interface-typed bag is accepted
205
- * @param {...RefValue<Element>} outer_refs
206
- * @returns {SpreadProps | null | undefined}
207
- */
208
- export function normalize_spread_props(props, ...outer_refs) {
209
- if (props == null) {
210
- return props;
211
- }
212
-
213
- const source = /** @type {SpreadProps} */ (props);
214
- /** @type {Array<RefValue<Element>>} */
215
- const refs = [];
216
- /** @type {SpreadProps} */
217
- const next = {};
218
- let changed = false;
219
- let existing_ref;
220
-
221
- for (const key of Reflect.ownKeys(source)) {
222
- const descriptor = get_descriptor(source, key);
223
- if (!descriptor?.enumerable) {
224
- continue;
225
- }
226
-
227
- const value = source[key];
228
-
229
- if (key === 'ref') {
230
- if (is_ref_prop(value)) {
231
- refs.push(value);
232
- changed = true;
233
- } else {
234
- existing_ref = /** @type {RefValue<Element>} */ (value);
235
- }
236
- continue;
237
- }
238
-
239
- if (is_ref_prop(value)) {
240
- refs.push(value);
241
- changed = true;
242
- continue;
243
- }
244
-
245
- next[key] = value;
246
- }
247
-
248
- if (!changed && outer_refs.length === 0) {
249
- return source;
250
- }
251
-
252
- const merged_ref = merge_ref_props(existing_ref, ...refs, ...outer_refs);
253
- if (merged_ref !== undefined) {
254
- next.ref = merged_ref;
255
- }
256
-
257
- return next;
258
- }
259
-
260
- /**
261
- * Normalize spread props for targets that read refs through an explicit
262
- * `ref={normalized.ref}` attribute. The returned `ref` stays readable for that
263
- * attribute but is non-enumerable so `{...normalized}` does not also pass it as
264
- * a DOM prop.
265
- *
266
- * @param {object | null | undefined} props
267
- * @param {...RefValue<Element>} outer_refs
268
- * @returns {SpreadProps | null | undefined}
269
- */
270
- export function normalize_spread_props_for_ref_attr(props, ...outer_refs) {
271
- const next = normalize_spread_props(props, ...outer_refs);
272
- if (next == null || !has_own_property.call(next, 'ref')) {
273
- return next;
274
- }
275
-
276
- const ref = next.ref;
277
- const without_ref = { ...next };
278
- delete without_ref.ref;
279
- Object.defineProperty(without_ref, 'ref', {
280
- value: ref,
281
- enumerable: false,
282
- configurable: true,
283
- writable: true,
284
- });
285
- return without_ref;
286
- }
287
-
288
- /**
289
- * @template {'current' | 'value'} K
290
- * @param {object} value
291
- * @param {K} key
292
- * @returns {value is Record<K, unknown>}
293
- */
294
- function is_ref_object(value, key) {
295
- if (is_dom_node(value)) {
296
- return false;
297
- }
298
- if (key === 'value' && '__v_isRef' in value) {
299
- return true;
300
- }
301
- if (has_own_property.call(value, key)) {
302
- return true;
303
- }
304
- return key === 'value' && has_prototype_accessor(value, 'value');
305
- }
306
-
307
- /**
308
- * @param {object} value
309
- * @returns {boolean}
310
- */
311
- function is_dom_node(value) {
312
- return (
313
- (typeof Node !== 'undefined' && value instanceof Node) ||
314
- ('nodeType' in value &&
315
- typeof (/** @type {{ nodeType?: unknown }} */ (value).nodeType) === 'number' &&
316
- 'nodeName' in value &&
317
- typeof (/** @type {{ nodeName?: unknown }} */ (value).nodeName) === 'string')
318
- );
319
- }
1
+ export * from '@tsrx/runtime/ref';
@@ -2,7 +2,13 @@
2
2
  /** @import * as ESRap from 'esrap' */
3
3
 
4
4
  import tsx from 'esrap/languages/tsx';
5
- import { should_preserve_comment, format_comment } from '../../comment-utils.js';
5
+ import {
6
+ should_preserve_comment,
7
+ should_preserve_jsx_tooling_comment,
8
+ is_file_level_pragma,
9
+ format_comment,
10
+ } from '../../comment-utils.js';
11
+ import { has_location } from '../../utils/ast.js';
6
12
  import { with_deferred_imports } from '../imports.js';
7
13
 
8
14
  /**
@@ -69,25 +75,64 @@ export function set_node_path_metadata(node, path) {
69
75
  * (structural tokens carry one-character source locations). typeOnly/volar
70
76
  * prints opt in — their maps are consumed positionally by the language
71
77
  * tooling and never shipped; build prints stay sparse.
72
- * @param {AST.CommentWithLocation[]} [comments] Source comments; the ones
73
- * `should_preserve_comment` classifies as semantic-to-TS (`@ts-nocheck`,
74
- * `@jsxImportSource`, triple-slash references, …) and that LEAD the program
75
- * are re-emitted at the top of the printed output. The generated TSX is real
76
- * TS input dropping a leading pragma changes how the whole file checks.
78
+ * @param {AST.CommentWithLocation[]} [comments] Source comments. In type-only
79
+ * output, file-wide pragmas lead the program while documentation and scoped
80
+ * annotations stay with their declarations, members, or statements. A sparse
81
+ * print with explicitly supplied comments retains its existing leading-pragma
82
+ * behavior. Ordinary build callers supply no comments.
77
83
  */
78
84
  export function tsx_with_ts_locations(boundary_tokens = false, comments = undefined) {
79
85
  const base = with_deferred_imports(tsx({ boundaryTokens: boundary_tokens }));
80
86
  const { _: base_visitor, ...base_visitors } = base;
87
+ const preserve_comments = comments !== undefined;
88
+ const preserve_owner_comments = boundary_tokens && preserve_comments;
89
+ /** @type {Set<string> | null} */
90
+ const emitted_comments = preserve_comments ? new Set() : null;
91
+
92
+ /**
93
+ * @param {AST.CommentWithLocation} comment
94
+ * @param {ESRap.Context} context
95
+ */
96
+ const write_preserved_comment = (comment, context) => {
97
+ if (
98
+ !emitted_comments ||
99
+ !(preserve_owner_comments
100
+ ? should_preserve_jsx_tooling_comment(comment)
101
+ : should_preserve_comment(comment))
102
+ ) {
103
+ return;
104
+ }
105
+ const key = `${comment.start}:${comment.end}:${comment.type}:${comment.value}`;
106
+ if (emitted_comments.has(key)) return;
107
+ emitted_comments.add(key);
108
+ if (comment.loc) context.location(comment.loc.start.line, comment.loc.start.column);
109
+ context.write(format_comment(comment));
110
+ if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
111
+ context.newline();
112
+ };
113
+
114
+ /**
115
+ * @param {AST.Node} node
116
+ * @param {ESRap.Context} context
117
+ */
118
+ const write_leading_comments = (node, context) => {
119
+ if (!node.leadingComments || !is_comment_owner(node)) return;
120
+ for (const comment of node.leadingComments) {
121
+ if (has_location(comment)) write_preserved_comment(comment, context);
122
+ }
123
+ };
81
124
 
82
125
  const leading_preserved = (/** @type {AST.Program} */ program) => {
83
- if (!comments?.length) return [];
126
+ if (!preserve_comments || !comments?.length) return [];
84
127
  // Injected statements (dynamic-import/try-import prepends) carry no
85
128
  // loc; anchor "leading" on the first statement that maps to source,
86
129
  // else every preserved comment in the file would hoist to the top.
87
130
  const first = program.body.find((node) => node.loc);
88
131
  return comments.filter(
89
132
  (comment) =>
90
- should_preserve_comment(comment) &&
133
+ (preserve_owner_comments
134
+ ? is_file_level_pragma(comment)
135
+ : should_preserve_comment(comment)) &&
91
136
  (first?.loc == null ||
92
137
  (comment.loc &&
93
138
  (comment.loc.end.line < first.loc.start.line ||
@@ -100,10 +145,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
100
145
  const wrappers = {
101
146
  Program: (node, context) => {
102
147
  for (const comment of leading_preserved(node)) {
103
- if (comment.loc) context.location(comment.loc.start.line, comment.loc.start.column);
104
- context.write(format_comment(comment));
105
- if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
106
- context.newline();
148
+ write_preserved_comment(comment, context);
107
149
  }
108
150
  /** @type {NonNullable<typeof base.Program>} */ (base.Program)(node, context);
109
151
  },
@@ -206,8 +248,13 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
206
248
  context.visit(node.body);
207
249
  },
208
250
  _(node, context, visit) {
251
+ if (preserve_owner_comments) write_leading_comments(node, context);
209
252
  const visit_with_locations = () => {
210
- if (!LOCATION_WRAPPED_NODE_TYPES.has(node.type) || !node.loc) {
253
+ if (
254
+ !node.loc ||
255
+ (!LOCATION_WRAPPED_NODE_TYPES.has(node.type) &&
256
+ !(boundary_tokens && TOOLING_LOCATION_WRAPPED_NODE_TYPES.has(node.type)))
257
+ ) {
211
258
  visit(node);
212
259
  return;
213
260
  }
@@ -226,6 +273,44 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
226
273
  return { ...base_visitors, ...wrappers };
227
274
  }
228
275
 
276
+ /**
277
+ * A newline is safe before a statement/declaration or a member, but not before
278
+ * an arbitrary expression: `return /** @type {number} *\/ 1` must not become a
279
+ * bare return, and `throw` forbids a line terminator before its argument.
280
+ * @param {AST.Node} node
281
+ * @returns {boolean}
282
+ */
283
+ function is_comment_owner(node) {
284
+ return (
285
+ node.type.endsWith('Declaration') ||
286
+ node.type.endsWith('Statement') ||
287
+ COMMENT_OWNER_NODE_TYPES.has(node.type)
288
+ );
289
+ }
290
+
291
+ const COMMENT_OWNER_NODE_TYPES = new Set([
292
+ 'VariableDeclarator',
293
+ 'Property',
294
+ 'PropertyDefinition',
295
+ 'AccessorProperty',
296
+ 'MethodDefinition',
297
+ 'TSAbstractPropertyDefinition',
298
+ 'TSAbstractAccessorProperty',
299
+ 'TSAbstractMethodDefinition',
300
+ 'TSPropertySignature',
301
+ 'TSMethodSignature',
302
+ 'TSIndexSignature',
303
+ 'TSEnumMember',
304
+ 'TSExportAssignment',
305
+ ]);
306
+
307
+ const TOOLING_LOCATION_WRAPPED_NODE_TYPES = new Set([
308
+ 'ExportNamedDeclaration',
309
+ 'ExportDefaultDeclaration',
310
+ 'ExportAllDeclaration',
311
+ 'TSPropertySignature',
312
+ ]);
313
+
229
314
  // Be careful when adding visitors that are already defined in `wrappers`.
230
315
  // JSXOpeningElement is intentionally in both places: its custom printer still
231
316
  // needs a location marker around the whole node.
@@ -561,7 +561,17 @@ export function createJsxTransform(platform) {
561
561
  * @returns {JsxTransformResult}
562
562
  */
563
563
  function transform(ast, source, filename, options) {
564
- const suspense_source = options?.suspenseSource ?? platform.imports.suspense;
564
+ const effective_platform =
565
+ options?.runtimeImports === 'direct' && platform.directRuntimeImports
566
+ ? {
567
+ ...platform,
568
+ imports: {
569
+ ...platform.imports,
570
+ ...platform.directRuntimeImports,
571
+ },
572
+ }
573
+ : platform;
574
+ const suspense_source = options?.suspenseSource ?? effective_platform.imports.suspense;
565
575
  const collect = !!(options?.collect || options?.loose);
566
576
  /** @type {AST.CSS.StyleSheet[]} */
567
577
  const stylesheets = [];
@@ -570,7 +580,7 @@ export function createJsxTransform(platform) {
570
580
 
571
581
  /** @type {TransformContext} */
572
582
  const transform_context = {
573
- platform,
583
+ platform: effective_platform,
574
584
  local_statement_component_index: 0,
575
585
  needs_error_boundary: false,
576
586
  needs_suspense: false,
@@ -811,7 +821,7 @@ export function createJsxTransform(platform) {
811
821
  if (platform.hooks?.injectImports) {
812
822
  platform.hooks.injectImports(expanded, transform_context, suspense_source);
813
823
  } else {
814
- inject_try_imports(expanded, transform_context, platform, suspense_source);
824
+ inject_try_imports(expanded, transform_context, effective_platform, suspense_source);
815
825
  }
816
826
 
817
827
  // Lower any `@{ … }` code blocks left in generated helper bodies before the
@@ -34,11 +34,19 @@ import {
34
34
  build_line_offsets,
35
35
  get_mapping_from_node,
36
36
  } from '../source-map-utils.js';
37
- import { should_preserve_comment } from '../comment-utils.js';
37
+ import { should_preserve_jsx_tooling_comment, format_comment } from '../comment-utils.js';
38
38
  import { has_location } from '../utils/ast.js';
39
39
 
40
40
  const LAZY_PARAM_IDENTIFIER_REGEX = /^__lazy\d+$/;
41
41
  const RETURN_KEYWORD = 'return';
42
+ const EXPORT_KEYWORD = 'export';
43
+ const BLOCK_DECLARATION_TYPES = new Set([
44
+ 'FunctionDeclaration',
45
+ 'ClassDeclaration',
46
+ 'TSInterfaceDeclaration',
47
+ 'TSEnumDeclaration',
48
+ 'TSModuleDeclaration',
49
+ ]);
42
50
 
43
51
  /**
44
52
  * @param {string} value
@@ -418,6 +426,88 @@ export function convert_source_map_to_mappings(
418
426
  return candidates[Math.min(index, candidates.length - 1)];
419
427
  }
420
428
 
429
+ /**
430
+ * A comment's end can share a source coordinate with the next declaration,
431
+ * and synthetic file pragmas can share offset zero with the first export.
432
+ * Select the position that actually prints this node's opening text instead
433
+ * of treating the first source-map entry as an unambiguous boundary.
434
+ * @param {AST.Position} position
435
+ * @param {string} text
436
+ * @returns {number | undefined}
437
+ */
438
+ function generated_offset_for_text(position, text) {
439
+ const positions = src_to_gen_map.get(`${position.line}:${position.column}`);
440
+ for (const generated of positions ?? []) {
441
+ const offset = loc_to_offset(generated.line, generated.column, gen_line_offsets);
442
+ if (generated_code.startsWith(text, offset)) return offset;
443
+ }
444
+ }
445
+
446
+ /**
447
+ * @param {AST.NodeWithLocation} node
448
+ * @param {string} start_text
449
+ * @returns {CodeMapping | undefined}
450
+ */
451
+ function declaration_mapping(node, start_text) {
452
+ const start = generated_offset_for_text(node.loc.start, start_text);
453
+ if (start === undefined) return;
454
+ const positions = src_to_gen_map.get(`${node.loc.end.line}:${node.loc.end.column}`);
455
+ for (const generated of positions ?? []) {
456
+ const end = loc_to_offset(generated.line, generated.column, gen_line_offsets);
457
+ if (end < start) continue;
458
+ return {
459
+ sourceOffsets: [node.start],
460
+ lengths: [node.end - node.start],
461
+ generatedOffsets: [start],
462
+ generatedLengths: [end - start],
463
+ data: { ...mapping_data_verify_only, customData: {} },
464
+ };
465
+ }
466
+ }
467
+
468
+ /** @param {AST.ExportNamedDeclaration | AST.ExportDefaultDeclaration | AST.ExportAllDeclaration} node */
469
+ function add_export_mapping(node) {
470
+ if (!has_location(node)) return;
471
+ const mapping = declaration_mapping(node, EXPORT_KEYWORD);
472
+ if (mapping) {
473
+ const declaration = node.type === 'ExportAllDeclaration' ? null : node.declaration;
474
+ const end = mapping.generatedOffsets[0] + mapping.generatedLengths[0];
475
+ // A semicolon-free source declaration shares its end with its last
476
+ // expression. The first map entry then precedes the statement's emitted
477
+ // semicolon. Block declarations are different: a following `;` is an
478
+ // empty statement, not part of TypeScript's declaration range.
479
+ if (
480
+ generated_code[end] === ';' &&
481
+ (!declaration || !BLOCK_DECLARATION_TYPES.has(declaration.type))
482
+ ) {
483
+ mapping.generatedLengths[0]++;
484
+ }
485
+ // Full declaration queries need both endpoints in the same Volar
486
+ // mapping, not a linear claim over the generated body. A transformed
487
+ // component can contain synthetic imports, tags, and helper calls that
488
+ // must not acquire source locations merely because it is exported.
489
+ const generated_start = mapping.generatedOffsets[0];
490
+ const generated_end = generated_start + mapping.generatedLengths[0];
491
+ mapping.sourceOffsets = [node.start, node.end];
492
+ mapping.generatedOffsets = [generated_start, generated_end];
493
+ mapping.lengths = [0, 0];
494
+ mapping.generatedLengths = [0, 0];
495
+ mappings.push(mapping);
496
+ }
497
+ tokens.push({
498
+ source: EXPORT_KEYWORD,
499
+ generated: EXPORT_KEYWORD,
500
+ loc: {
501
+ start: node.loc.start,
502
+ end: {
503
+ line: node.loc.start.line,
504
+ column: node.loc.start.column + EXPORT_KEYWORD.length,
505
+ },
506
+ },
507
+ metadata: {},
508
+ });
509
+ }
510
+
421
511
  /**
422
512
  * Needed for a mapping that includes the computed brackets for diagnostics
423
513
  * @param {AST.MethodDefinition | AST.Property} node
@@ -464,25 +554,24 @@ export function convert_source_map_to_mappings(
464
554
  if (!Array.isArray(comments)) continue;
465
555
 
466
556
  for (const comment of comments) {
467
- if (!has_location(comment) || !should_preserve_comment(comment)) continue;
557
+ if (!has_location(comment) || !should_preserve_jsx_tooling_comment(comment)) continue;
468
558
 
469
559
  const comment_key = `${comment.start}:${comment.end}`;
470
560
  if (mapped_comments.has(comment_key)) continue;
471
561
  mapped_comments.add(comment_key);
472
562
 
473
- try {
474
- mappings.push(
475
- get_mapping_from_node(
476
- comment,
477
- src_to_gen_map,
478
- gen_line_offsets,
479
- mapping_data_verify_only,
480
- ),
481
- );
482
- } catch {
483
- // Comments that were not emitted in generated TSX have no source-map
484
- // segment. They should not produce Volar mappings.
485
- }
563
+ const text = format_comment(comment);
564
+ const start = generated_offset_for_text(comment.loc.start, text);
565
+ // A stripped or moved comment must not claim another node's shared
566
+ // source coordinate. The printer writes this exact formatted text.
567
+ if (start === undefined) continue;
568
+ mappings.push({
569
+ sourceOffsets: [comment.start],
570
+ lengths: [comment.end - comment.start],
571
+ generatedOffsets: [start],
572
+ generatedLengths: [text.length],
573
+ data: { ...mapping_data_verify_only, customData: {} },
574
+ });
486
575
  }
487
576
  }
488
577
  }
@@ -704,6 +793,7 @@ export function convert_source_map_to_mappings(
704
793
  }
705
794
  return;
706
795
  } else if (node.type === 'ExportNamedDeclaration') {
796
+ add_export_mapping(node);
707
797
  if (node.specifiers && node.specifiers.length > 0) {
708
798
  for (const specifier of node.specifiers) {
709
799
  visit(specifier);
@@ -715,12 +805,14 @@ export function convert_source_map_to_mappings(
715
805
  }
716
806
  return;
717
807
  } else if (node.type === 'ExportDefaultDeclaration') {
808
+ add_export_mapping(node);
718
809
  // Visit the declaration
719
810
  if (node.declaration) {
720
811
  visit(/** @type {AST.Node} */ (node.declaration));
721
812
  }
722
813
  return;
723
814
  } else if (node.type === 'ExportAllDeclaration') {
815
+ add_export_mapping(node);
724
816
  // Nothing to visit (just source string)
725
817
  return;
726
818
  } else if (node.type === 'JSXOpeningElement') {
@@ -1908,6 +2000,24 @@ export function convert_source_map_to_mappings(
1908
2000
  }
1909
2001
  return;
1910
2002
  } else if (node.type === 'TSPropertySignature') {
2003
+ if (has_location(node)) {
2004
+ const start_text = node.readonly
2005
+ ? 'readonly'
2006
+ : node.computed
2007
+ ? '['
2008
+ : node.key.type === 'Identifier'
2009
+ ? node.key.name
2010
+ : source.slice(node.start, node.key.end);
2011
+ const mapping = declaration_mapping(node, start_text);
2012
+ if (mapping) {
2013
+ const end = mapping.generatedOffsets[0] + mapping.generatedLengths[0];
2014
+ // esrap's containing type/interface prints member separators
2015
+ // after the property's own end marker. TS includes that `;`
2016
+ // in its PropertySignature range, even when it was not authored.
2017
+ if (generated_code[end] === ';') mapping.generatedLengths[0]++;
2018
+ mappings.push(mapping);
2019
+ }
2020
+ }
1911
2021
  // Property signature in type
1912
2022
  if (node.key) {
1913
2023
  visit(node.key);
@@ -2354,7 +2464,11 @@ export function convert_source_map_to_mappings(
2354
2464
 
2355
2465
  // Add a mapping for the very beginning of the file to handle import additions
2356
2466
  // This ensures that code actions adding imports at the top work correctly
2357
- if (!isImportDeclarationPresent && mappings.length > 0 && mappings[0].sourceOffsets[0] > 0) {
2467
+ if (
2468
+ !isImportDeclarationPresent &&
2469
+ mappings.length > 0 &&
2470
+ (mappings[0].sourceOffsets[0] > 0 || mappings[0].generatedOffsets[0] > 0)
2471
+ ) {
2358
2472
  mappings.unshift({
2359
2473
  sourceOffsets: [0],
2360
2474
  generatedOffsets: [0],
package/types/index.d.ts CHANGED
@@ -2218,6 +2218,14 @@ export interface VolarCompileOptions extends Omit<ParseOptions, 'errors' | 'comm
2218
2218
  dev?: boolean;
2219
2219
  }
2220
2220
 
2221
+ /**
2222
+ * Selects where generated runtime helper imports resolve from. Direct mode
2223
+ * emits bare imports from the target's standalone runtime package; the package
2224
+ * that owns the generated modules must declare that runtime as a direct
2225
+ * production dependency.
2226
+ */
2227
+ export type RuntimeImportMode = 'compiler' | 'direct';
2228
+
2221
2229
  /**
2222
2230
  * Common base options accepted by every TSRX target's `compile` entry point.
2223
2231
  * Targets that need extra knobs (e.g. ripple's `mode`/`dev`/`hmr`, preact's
@@ -2227,6 +2235,13 @@ export interface VolarCompileOptions extends Omit<ParseOptions, 'errors' | 'comm
2227
2235
  export interface BaseCompileOptions {
2228
2236
  collect?: boolean;
2229
2237
  loose?: boolean;
2238
+ /**
2239
+ * Selects where generated runtime helper imports resolve from. The default
2240
+ * `'compiler'` mode preserves compiler-package compatibility subpaths;
2241
+ * `'direct'` targets the renderer's small runtime package, which the package
2242
+ * owning the generated modules must declare as a direct production dependency.
2243
+ */
2244
+ runtimeImports?: RuntimeImportMode;
2230
2245
  }
2231
2246
 
2232
2247
  /**
@@ -1,7 +1,7 @@
1
1
  import type * as AST from 'estree';
2
2
  import type * as ESTreeJSX from 'estree-jsx';
3
3
  import type { RawSourceMap } from 'source-map';
4
- import type { CompileError, JsxHelperComponent, JsxHelperState } from './index';
4
+ import type { CompileError, JsxHelperComponent, JsxHelperState, RuntimeImportMode } from './index';
5
5
 
6
6
  /**
7
7
  * Result returned by a JSX platform transform (React, Preact, Solid).
@@ -82,6 +82,13 @@ export interface JsxTransformContext {
82
82
  * Optional per-call compile options passed to a created JSX transform.
83
83
  */
84
84
  export interface JsxTransformOptions {
85
+ /**
86
+ * Selects compiler-package compatibility imports or direct renderer runtime
87
+ * package imports. Defaults to `'compiler'`. Direct mode requires the package
88
+ * owning generated modules to declare the target runtime as a direct
89
+ * production dependency.
90
+ */
91
+ runtimeImports?: RuntimeImportMode;
85
92
  /**
86
93
  * Override the import source used for `Suspense` in try-block transforms.
87
94
  * Falls back to `platform.imports.suspense`. Preact uses this to let the
@@ -412,6 +419,17 @@ export interface JsxPlatform {
412
419
  forOfIterableHelper?: string;
413
420
  };
414
421
 
422
+ /**
423
+ * Runtime-helper import sources used when a compile call opts into direct
424
+ * runtime imports. Fields omitted here continue using `imports`.
425
+ */
426
+ directRuntimeImports?: {
427
+ errorBoundary?: string;
428
+ mergeRefs?: string;
429
+ refProp?: string;
430
+ forOfIterableHelper?: string;
431
+ };
432
+
415
433
  jsx: {
416
434
  /**
417
435
  * Rewrite Ripple's `class` attribute to `className` for legacy targets
package/types/parse.d.ts CHANGED
@@ -679,6 +679,12 @@ export namespace Parse {
679
679
  */
680
680
  getTokenFromCodeInType(code: number): void;
681
681
 
682
+ /**
683
+ * Get the current tokenizer state used by lookahead parsing.
684
+ * Added by @sveltejs/acorn-typescript.
685
+ */
686
+ getCurLookaheadState(): LookaheadState;
687
+
682
688
  /**
683
689
  * Get current position as Position object
684
690
  * @returns { line: number, column: number, index: number }
@@ -1,13 +1 @@
1
- // Helper type for item in an array or iterable
2
- // example: IterationValue<typeof something>
3
- export type IterationValue<T> = T extends readonly unknown[]
4
- ? T[number]
5
- : T extends Iterable<infer U>
6
- ? U
7
- : never;
8
-
9
- export function map_iterable<T, U>(
10
- value: Iterable<T>,
11
- fn: (item: T, index: number, is_last: boolean) => U,
12
- tail?: () => U | U[],
13
- ): U[];
1
+ export * from '@tsrx/runtime/iterable';
@@ -1,30 +1 @@
1
- export const get_descriptor: typeof Object.getOwnPropertyDescriptor;
2
- export const get_descriptors: typeof Object.getOwnPropertyDescriptors;
3
- export const array_from: typeof Array.from;
4
- export const is_array: typeof Array.isArray;
5
- export const define_property: typeof Object.defineProperty;
6
- export const get_prototype_of: typeof Object.getPrototypeOf;
7
- export const object_values: typeof Object.values;
8
- export const object_entries: typeof Object.entries;
9
- export const object_keys: typeof Object.keys;
10
- export const get_own_property_symbols: typeof Object.getOwnPropertySymbols;
11
- export const structured_clone: typeof structuredClone;
12
- export const object_prototype: typeof Object.prototype;
13
- export const array_prototype: typeof Array.prototype;
14
- export const has_own_property: typeof Object.prototype.hasOwnProperty;
15
-
16
- export function has_prototype_accessor(value: object, key: PropertyKey): boolean;
17
- export function array_slice<T>(array_like: ArrayLike<T>, ...args: number[]): T[];
18
- export function iterable_array_from<T>(
19
- iterable: Iterable<T> | Iterator<T> | ArrayLike<T>,
20
- index?: number,
21
- ): T[];
22
- /**
23
- * The props bag minus one prop. Constrained to `object` rather than an index
24
- * signature so an interface- or class-typed props bag is accepted; returns `{}`
25
- * when `props` is nullish.
26
- */
27
- export function exclude_prop_from_object<
28
- T extends object = Record<PropertyKey, unknown>,
29
- K extends PropertyKey = PropertyKey,
30
- >(props: T | null | undefined, exclude_prop: K): Omit<T, K>;
1
+ export * from '@tsrx/runtime/language-helpers';
@@ -1,88 +1 @@
1
- export type MergeableRefCallback<T> = {
2
- bivarianceHack(node: T | null): void | (() => void);
3
- }['bivarianceHack'];
4
- export type MergeableRefObject<T> = { current: T | null };
5
- export type MergeableVueRef<T> = { value: T | null };
6
- export type RefProp<T = unknown> = (node: T | null) => void | (() => void);
7
- export type RefValue<T = Element> =
8
- | ((node: T) => void | (() => void))
9
- | readonly RefValue<T>[]
10
- | { current: T | null }
11
- | { value: T | null }
12
- | T
13
- | null
14
- | undefined;
15
-
16
- export type MergeableRef<T> =
17
- MergeableRefCallback<T> | MergeableRefObject<T> | MergeableVueRef<T> | null | undefined;
18
-
19
- /**
20
- * A props bag as it reaches a spread: keys the caller has not narrowed.
21
- *
22
- * Accepting bag is spelled `object` at the parameter position — an index
23
- * signature would reject an interface- or class-typed props bag — while this
24
- * is what the helpers hand back when they rebuild one.
25
- */
26
- export type SpreadProps = Record<PropertyKey, unknown>;
27
-
28
- /**
29
- * The node a ref value points at, derived from the ref's own type in the order
30
- * the runtime resolves it (see `apply_ref_value` and `is_ref_object`): a list
31
- * resolves through its entries, a callback through its parameter, and a DOM
32
- * node is never treated as a ref object.
33
- *
34
- * Inferring through `RefValue<T>` instead would read the target off the wrong
35
- * union member: TypeScript prefers a structural member over a naked type
36
- * parameter, so `HTMLInputElement` would match the `{ value: T | null }`
37
- * (Vue ref) branch and resolve `T` to `string`.
38
- */
39
- export type RefTarget<V> = [V] extends [never]
40
- ? never
41
- : V extends null | undefined
42
- ? never
43
- : V extends (node: infer N) => unknown
44
- ? N
45
- : V extends readonly (infer E)[]
46
- ? RefTarget<E>
47
- : V extends Node
48
- ? V
49
- : V extends { current: infer C }
50
- ? NonNullable<C>
51
- : V extends { value: infer C }
52
- ? NonNullable<C>
53
- : V;
54
-
55
- export function mergeRefs<T = Element>(
56
- ...refs: Array<MergeableRef<T>>
57
- ): (node: T | null) => () => void;
58
- export function isRefProp(value: unknown): boolean;
59
- // The inference overload comes first so an inferred call resolves the target
60
- // through `RefTarget`; the `T` overload below still serves explicit type
61
- // arguments, which is what every compiler-generated call passes.
62
- export function create_ref_prop<V>(
63
- get_ref_value: () => V,
64
- set_ref_value?: (value: RefTarget<V> | null) => void,
65
- ): RefProp<RefTarget<V>>;
66
- export function create_ref_prop<T = Element>(
67
- get_ref_value: () => RefValue<T>,
68
- set_ref_value?: (value: T | null) => void,
69
- ): RefProp<T>;
70
- export function apply_ref_value<V>(
71
- ref_value: V,
72
- node: RefTarget<V> | null,
73
- set_ref_value?: (value: RefTarget<V> | null) => void,
74
- ): void | (() => void);
75
- export function apply_ref_value<T = Element>(
76
- ref_value: RefValue<T>,
77
- node: T | null,
78
- set_ref_value?: (value: T | null) => void,
79
- ): void | (() => void);
80
- export function merge_ref_props<T = Element>(...refs: Array<RefValue<T>>): RefValue<T>;
81
- export function normalize_spread_props<T extends object | null | undefined>(
82
- props: T,
83
- ...outer_refs: Array<RefValue<Element>>
84
- ): T | SpreadProps;
85
- export function normalize_spread_props_for_ref_attr<T extends object | null | undefined>(
86
- props: T,
87
- ...outer_refs: Array<RefValue<Element>>
88
- ): T | SpreadProps;
1
+ export * from '@tsrx/runtime/ref';