ripple 0.2.90 → 0.2.91
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 +1 -1
- package/src/compiler/phases/1-parse/index.js +19 -13
- package/src/compiler/phases/3-transform/client/index.js +37 -17
- package/src/compiler/phases/3-transform/segments.js +194 -34
- package/src/runtime/date.js +73 -0
- package/src/runtime/index-client.js +23 -21
- package/tests/client/date.test.ripple +392 -0
- package/types/index.d.ts +35 -28
package/package.json
CHANGED
|
@@ -455,24 +455,30 @@ function RipplePlugin(config) {
|
|
|
455
455
|
jsx_parseExpressionContainer() {
|
|
456
456
|
let node = this.startNode();
|
|
457
457
|
this.next();
|
|
458
|
-
|
|
458
|
+
let tracked = false;
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
460
|
+
if (this.value === 'html') {
|
|
461
|
+
node.html = true;
|
|
462
|
+
this.next();
|
|
463
|
+
if (this.type === tt.braceR) {
|
|
464
|
+
this.raise(
|
|
465
|
+
this.start,
|
|
466
|
+
'"html" is a Ripple keyword and must be used in the form {html some_content}',
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (this.type.label === '@') {
|
|
470
|
+
this.next(); // consume @
|
|
471
|
+
tracked = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
468
474
|
|
|
469
475
|
node.expression =
|
|
470
476
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
471
477
|
this.expect(tt.braceR);
|
|
472
478
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
479
|
+
if (tracked && node.expression.type === 'Identifier') {
|
|
480
|
+
node.expression.tracked = true;
|
|
481
|
+
}
|
|
476
482
|
|
|
477
483
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
478
484
|
}
|
|
@@ -970,7 +976,7 @@ function RipplePlugin(config) {
|
|
|
970
976
|
if (this.type.label === '{') {
|
|
971
977
|
const node = this.jsx_parseExpressionContainer();
|
|
972
978
|
node.type = node.html ? 'Html' : 'Text';
|
|
973
|
-
|
|
979
|
+
delete node.html;
|
|
974
980
|
body.push(node);
|
|
975
981
|
} else if (this.type.label === '}') {
|
|
976
982
|
return;
|
|
@@ -46,8 +46,7 @@ function add_ripple_internal_import(context) {
|
|
|
46
46
|
|
|
47
47
|
function visit_function(node, context) {
|
|
48
48
|
if (context.state.to_ts) {
|
|
49
|
-
context.next(context.state);
|
|
50
|
-
return;
|
|
49
|
+
return context.next(context.state);
|
|
51
50
|
}
|
|
52
51
|
const metadata = node.metadata;
|
|
53
52
|
const state = context.state;
|
|
@@ -896,7 +895,11 @@ const visitors = {
|
|
|
896
895
|
}),
|
|
897
896
|
];
|
|
898
897
|
|
|
899
|
-
return b.function(
|
|
898
|
+
return b.function(
|
|
899
|
+
node.id,
|
|
900
|
+
node.params.map((param) => context.visit(param, { ...context.state, metadata })),
|
|
901
|
+
b.block(body_statements),
|
|
902
|
+
);
|
|
900
903
|
}
|
|
901
904
|
|
|
902
905
|
let props = b.id('__props');
|
|
@@ -1000,8 +1003,7 @@ const visitors = {
|
|
|
1000
1003
|
|
|
1001
1004
|
UpdateExpression(node, context) {
|
|
1002
1005
|
if (context.state.to_ts) {
|
|
1003
|
-
context.next();
|
|
1004
|
-
return;
|
|
1006
|
+
return context.next();
|
|
1005
1007
|
}
|
|
1006
1008
|
const argument = node.argument;
|
|
1007
1009
|
|
|
@@ -1047,8 +1049,7 @@ const visitors = {
|
|
|
1047
1049
|
|
|
1048
1050
|
ForOfStatement(node, context) {
|
|
1049
1051
|
if (!is_inside_component(context)) {
|
|
1050
|
-
context.next();
|
|
1051
|
-
return;
|
|
1052
|
+
return context.next();
|
|
1052
1053
|
}
|
|
1053
1054
|
const is_controlled = node.is_controlled;
|
|
1054
1055
|
const index = node.index;
|
|
@@ -1090,8 +1091,7 @@ const visitors = {
|
|
|
1090
1091
|
|
|
1091
1092
|
IfStatement(node, context) {
|
|
1092
1093
|
if (!is_inside_component(context)) {
|
|
1093
|
-
context.next();
|
|
1094
|
-
return;
|
|
1094
|
+
return context.next();
|
|
1095
1095
|
}
|
|
1096
1096
|
context.state.template.push('<!>');
|
|
1097
1097
|
|
|
@@ -1174,8 +1174,7 @@ const visitors = {
|
|
|
1174
1174
|
|
|
1175
1175
|
TryStatement(node, context) {
|
|
1176
1176
|
if (!is_inside_component(context)) {
|
|
1177
|
-
context.next();
|
|
1178
|
-
return;
|
|
1177
|
+
return context.next();
|
|
1179
1178
|
}
|
|
1180
1179
|
context.state.template.push('<!>');
|
|
1181
1180
|
|
|
@@ -1310,6 +1309,9 @@ function transform_ts_child(node, context) {
|
|
|
1310
1309
|
|
|
1311
1310
|
if (node.type === 'Text') {
|
|
1312
1311
|
state.init.push(b.stmt(visit(node.expression, { ...state })));
|
|
1312
|
+
} else if (node.type === 'Html') {
|
|
1313
|
+
// Do we need to do something special here?
|
|
1314
|
+
state.init.push(b.stmt(visit(node.expression, { ...state })));
|
|
1313
1315
|
} else if (node.type === 'Element') {
|
|
1314
1316
|
const type = node.id.name;
|
|
1315
1317
|
const children = [];
|
|
@@ -1329,12 +1331,25 @@ function transform_ts_child(node, context) {
|
|
|
1329
1331
|
if (attr.type === 'Attribute') {
|
|
1330
1332
|
const metadata = { await: false };
|
|
1331
1333
|
const name = visit(attr.name, { ...state, metadata });
|
|
1332
|
-
const value =
|
|
1333
|
-
|
|
1334
|
-
|
|
1334
|
+
const value =
|
|
1335
|
+
attr.value === null ? b.literal(true) : visit(attr.value, { ...state, metadata });
|
|
1336
|
+
|
|
1337
|
+
// Handle both regular identifiers and tracked identifiers
|
|
1338
|
+
let prop_name;
|
|
1339
|
+
if (name.type === 'Identifier') {
|
|
1340
|
+
prop_name = name.name;
|
|
1341
|
+
} else if (name.type === 'MemberExpression' && name.object.type === 'Identifier') {
|
|
1342
|
+
// For tracked attributes like {@count}, use the original name
|
|
1343
|
+
prop_name = name.object.name;
|
|
1344
|
+
} else {
|
|
1345
|
+
prop_name = attr.name.name || 'unknown';
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const jsx_name = b.jsx_id(prop_name);
|
|
1349
|
+
if (prop_name === 'children') {
|
|
1335
1350
|
has_children_props = true;
|
|
1336
1351
|
}
|
|
1337
|
-
jsx_name.loc = name.loc;
|
|
1352
|
+
jsx_name.loc = attr.name.loc || name.loc;
|
|
1338
1353
|
|
|
1339
1354
|
return b.jsx_attribute(jsx_name, b.jsx_expression_container(value));
|
|
1340
1355
|
} else if (attr.type === 'SpreadAttribute') {
|
|
@@ -1468,7 +1483,7 @@ function transform_ts_child(node, context) {
|
|
|
1468
1483
|
|
|
1469
1484
|
state.init.push(b.try(try_body, catch_handler, finally_block));
|
|
1470
1485
|
} else if (node.type === 'Component') {
|
|
1471
|
-
const component = visit(node,
|
|
1486
|
+
const component = visit(node, state);
|
|
1472
1487
|
|
|
1473
1488
|
state.init.push(component);
|
|
1474
1489
|
} else {
|
|
@@ -1684,7 +1699,12 @@ function transform_body(body, { visit, state }) {
|
|
|
1684
1699
|
transform_children(body, { visit, state: body_state, root: true });
|
|
1685
1700
|
|
|
1686
1701
|
if (body_state.update.length > 0) {
|
|
1687
|
-
|
|
1702
|
+
if (state.to_ts) {
|
|
1703
|
+
// In TypeScript mode, just add the update statements directly
|
|
1704
|
+
body_state.init.push(...body_state.update);
|
|
1705
|
+
} else {
|
|
1706
|
+
body_state.init.push(b.stmt(b.call('_$_.render', b.thunk(b.block(body_state.update)))));
|
|
1707
|
+
}
|
|
1688
1708
|
}
|
|
1689
1709
|
|
|
1690
1710
|
return [...body_state.setup, ...body_state.init, ...body_state.final];
|
|
@@ -7,14 +7,87 @@ export const mapping_data = {
|
|
|
7
7
|
navigation: true,
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Helper to find a meaningful token boundary by looking for word boundaries,
|
|
12
|
+
* punctuation, or whitespace
|
|
13
|
+
* @param {string} text
|
|
14
|
+
* @param {number} start
|
|
15
|
+
* @param {number} direction
|
|
16
|
+
*/
|
|
17
|
+
function findTokenBoundary(text, start, direction = 1) {
|
|
18
|
+
if (start < 0 || start >= text.length) return start;
|
|
19
|
+
|
|
20
|
+
let pos = start;
|
|
21
|
+
/** @param {string} c */
|
|
22
|
+
const isAlphaNum = (c) => /[a-zA-Z0-9_$]/.test(c);
|
|
23
|
+
|
|
24
|
+
// If we're at whitespace or punctuation, find the next meaningful character
|
|
25
|
+
while (pos >= 0 && pos < text.length && /\s/.test(text[pos])) {
|
|
26
|
+
pos += direction;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (pos < 0 || pos >= text.length) return start;
|
|
30
|
+
|
|
31
|
+
// If we're in the middle of a word/identifier, find the boundary
|
|
32
|
+
if (isAlphaNum(text[pos])) {
|
|
33
|
+
if (direction > 0) {
|
|
34
|
+
while (pos < text.length && isAlphaNum(text[pos])) pos++;
|
|
35
|
+
} else {
|
|
36
|
+
while (pos >= 0 && isAlphaNum(text[pos])) pos--;
|
|
37
|
+
pos++; // Adjust back to start of token
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
// For punctuation, just move one character in the given direction
|
|
41
|
+
pos += direction;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Math.max(0, Math.min(text.length, pos));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if source and generated content are meaningfully similar
|
|
49
|
+
* @param {string} sourceContent
|
|
50
|
+
* @param {string} generatedContent
|
|
51
|
+
*/
|
|
52
|
+
function isValidMapping(sourceContent, generatedContent) {
|
|
53
|
+
// Remove whitespace for comparison
|
|
54
|
+
const cleanSource = sourceContent.replace(/\s+/g, '');
|
|
55
|
+
const cleanGenerated = generatedContent.replace(/\s+/g, '');
|
|
56
|
+
|
|
57
|
+
// If either is empty, skip
|
|
58
|
+
if (!cleanSource || !cleanGenerated) return false;
|
|
59
|
+
|
|
60
|
+
// Skip obvious template transformations that don't make sense to map
|
|
61
|
+
const templateTransforms = [
|
|
62
|
+
/^\{.*\}$/, // Curly brace expressions
|
|
63
|
+
/^<.*>$/, // HTML tags
|
|
64
|
+
/^\(\(\)\s*=>\s*\{$/, // Generated function wrappers
|
|
65
|
+
/^\}\)\(\)\}$/, // Generated function closures
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const transform of templateTransforms) {
|
|
69
|
+
if (transform.test(cleanSource) || transform.test(cleanGenerated)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if content is similar (exact match, or generated contains source)
|
|
75
|
+
if (cleanSource === cleanGenerated) return true;
|
|
76
|
+
if (cleanGenerated.includes(cleanSource)) return true;
|
|
77
|
+
if (cleanSource.includes(cleanGenerated) && cleanGenerated.length > 2) return true;
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
10
82
|
/**
|
|
11
83
|
* Convert esrap SourceMap to Volar mappings
|
|
12
|
-
* @param {
|
|
84
|
+
* @param {{ mappings: string }} source_map
|
|
13
85
|
* @param {string} source
|
|
14
86
|
* @param {string} generated_code
|
|
15
87
|
* @returns {object}
|
|
16
88
|
*/
|
|
17
89
|
export function convert_source_map_to_mappings(source_map, source, generated_code) {
|
|
90
|
+
/** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
|
|
18
91
|
const mappings = [];
|
|
19
92
|
|
|
20
93
|
// Decode the VLQ mappings from esrap
|
|
@@ -22,6 +95,7 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
|
|
|
22
95
|
|
|
23
96
|
let generated_offset = 0;
|
|
24
97
|
const generated_lines = generated_code.split('\n');
|
|
98
|
+
const source_lines = source.split('\n');
|
|
25
99
|
|
|
26
100
|
// Process each line of generated code
|
|
27
101
|
for (let generated_line = 0; generated_line < generated_lines.length; generated_line++) {
|
|
@@ -38,7 +112,6 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
|
|
|
38
112
|
}
|
|
39
113
|
|
|
40
114
|
// Calculate source offset
|
|
41
|
-
const source_lines = source.split('\n');
|
|
42
115
|
let source_offset = 0;
|
|
43
116
|
for (let i = 0; i < Math.min(source_line, source_lines.length - 1); i++) {
|
|
44
117
|
source_offset += source_lines[i].length + 1; // +1 for newline
|
|
@@ -48,41 +121,125 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
|
|
|
48
121
|
// Calculate generated offset
|
|
49
122
|
const current_generated_offset = generated_offset + generated_column;
|
|
50
123
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
124
|
+
// Find meaningful token boundaries for source content
|
|
125
|
+
const source_token_end = findTokenBoundary(source, source_offset, 1);
|
|
126
|
+
const source_token_start = findTokenBoundary(source, source_offset, -1);
|
|
127
|
+
|
|
128
|
+
// Find meaningful token boundaries for generated content
|
|
129
|
+
const generated_token_end = findTokenBoundary(generated_code, current_generated_offset, 1);
|
|
130
|
+
const generated_token_start = findTokenBoundary(generated_code, current_generated_offset, -1);
|
|
131
|
+
|
|
132
|
+
// Extract potential source content (prefer forward boundary but try both directions)
|
|
133
|
+
let best_source_content = source.substring(source_offset, source_token_end);
|
|
134
|
+
let best_generated_content = generated_code.substring(current_generated_offset, generated_token_end);
|
|
135
|
+
|
|
136
|
+
// Try different segment boundaries to find the best match
|
|
137
|
+
const candidates = [
|
|
138
|
+
// Forward boundaries
|
|
139
|
+
{
|
|
140
|
+
source: source.substring(source_offset, source_token_end),
|
|
141
|
+
generated: generated_code.substring(current_generated_offset, generated_token_end)
|
|
142
|
+
},
|
|
143
|
+
// Backward boundaries
|
|
144
|
+
{
|
|
145
|
+
source: source.substring(source_token_start, source_offset + 1),
|
|
146
|
+
generated: generated_code.substring(generated_token_start, current_generated_offset + 1)
|
|
147
|
+
},
|
|
148
|
+
// Single character
|
|
149
|
+
{
|
|
150
|
+
source: source.charAt(source_offset),
|
|
151
|
+
generated: generated_code.charAt(current_generated_offset)
|
|
152
|
+
},
|
|
153
|
+
// Try to find exact matches in nearby content
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
// Look for the best candidate match
|
|
157
|
+
let best_match = null;
|
|
158
|
+
for (const candidate of candidates) {
|
|
159
|
+
if (isValidMapping(candidate.source, candidate.generated)) {
|
|
160
|
+
best_match = candidate;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
67
163
|
}
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
164
|
+
|
|
165
|
+
// If no good match found, try extracting identifiers/keywords
|
|
166
|
+
if (!best_match) {
|
|
167
|
+
const sourceIdMatch = source.substring(source_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
|
|
168
|
+
const generatedIdMatch = generated_code.substring(current_generated_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
|
|
169
|
+
|
|
170
|
+
if (sourceIdMatch && generatedIdMatch && sourceIdMatch[0] === generatedIdMatch[0]) {
|
|
171
|
+
best_match = {
|
|
172
|
+
source: sourceIdMatch[0],
|
|
173
|
+
generated: generatedIdMatch[0]
|
|
174
|
+
};
|
|
77
175
|
}
|
|
78
176
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
177
|
+
|
|
178
|
+
// Handle special cases for Ripple keywords that might not have generated equivalents
|
|
179
|
+
if (!best_match || best_match.source.length === 0) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Special handling for Ripple-specific syntax that may be omitted in generated code
|
|
184
|
+
const sourceAtOffset = source.substring(source_offset, source_offset + 10);
|
|
185
|
+
if (sourceAtOffset.includes('index ')) {
|
|
186
|
+
// For the 'index' keyword, create a mapping even if there's no generated equivalent
|
|
187
|
+
const indexMatch = sourceAtOffset.match(/index\s+/);
|
|
188
|
+
if (indexMatch) {
|
|
189
|
+
best_match = {
|
|
190
|
+
source: indexMatch[0].trim(),
|
|
191
|
+
generated: '' // Empty generated content for keywords that are transformed away
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Skip if we still don't have a valid source match
|
|
197
|
+
if (!best_match || best_match.source.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Skip mappings for complex RefAttribute syntax to avoid overlapping sourcemaps,
|
|
202
|
+
// but allow simple 'ref' keyword mappings for IntelliSense
|
|
203
|
+
if (best_match.source.includes('{ref ') && best_match.source.length > 10) {
|
|
204
|
+
// Skip complex ref expressions like '{ref (node) => { ... }}'
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Allow simple 'ref' keyword mappings for IntelliSense
|
|
209
|
+
if (best_match.source.trim() === 'ref' && best_match.generated.length === 0) {
|
|
210
|
+
// This is just the ref keyword, allow it for syntax support
|
|
211
|
+
// but map it to current position since there's no generated equivalent
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Calculate actual offsets and lengths for the best match
|
|
215
|
+
let actual_source_offset, actual_generated_offset;
|
|
216
|
+
|
|
217
|
+
if (best_match.generated.length > 0) {
|
|
218
|
+
actual_source_offset = source.indexOf(best_match.source, source_offset - best_match.source.length);
|
|
219
|
+
actual_generated_offset = generated_code.indexOf(best_match.generated, current_generated_offset - best_match.generated.length);
|
|
220
|
+
} else {
|
|
221
|
+
// For keywords with no generated equivalent, use the exact source position
|
|
222
|
+
actual_source_offset = source_offset;
|
|
223
|
+
actual_generated_offset = current_generated_offset; // Map to current position in generated code
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Use the match we found, but fall back to original positions if indexOf fails
|
|
227
|
+
const final_source_offset = actual_source_offset !== -1 ? actual_source_offset : source_offset;
|
|
228
|
+
const final_generated_offset = actual_generated_offset !== -1 ? actual_generated_offset : current_generated_offset; // Avoid duplicate mappings by checking if we already have this exact mapping
|
|
229
|
+
const isDuplicate = mappings.some(existing =>
|
|
230
|
+
existing.sourceOffsets[0] === final_source_offset &&
|
|
231
|
+
existing.generatedOffsets[0] === final_generated_offset &&
|
|
232
|
+
existing.lengths[0] === best_match.source.length
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (!isDuplicate) {
|
|
236
|
+
mappings.push({
|
|
237
|
+
sourceOffsets: [final_source_offset],
|
|
238
|
+
generatedOffsets: [final_generated_offset],
|
|
239
|
+
lengths: [best_match.source.length],
|
|
240
|
+
data: mapping_data,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
86
243
|
}
|
|
87
244
|
|
|
88
245
|
// Add line length + 1 for newline (except for last line)
|
|
@@ -92,6 +249,9 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
|
|
|
92
249
|
}
|
|
93
250
|
}
|
|
94
251
|
|
|
252
|
+
// Sort mappings by source offset for better organization
|
|
253
|
+
mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
|
|
254
|
+
|
|
95
255
|
return {
|
|
96
256
|
code: generated_code,
|
|
97
257
|
mappings,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** @import { Block, Derived } from '#client' */
|
|
2
|
+
import { safe_scope, tracked, get, derived, set } from './internal/client/runtime.js';
|
|
3
|
+
|
|
4
|
+
var init = false;
|
|
5
|
+
|
|
6
|
+
export class TrackedDate extends Date {
|
|
7
|
+
#time;
|
|
8
|
+
/** @type {Map<keyof Date, Derived>} */
|
|
9
|
+
#deriveds = new Map();
|
|
10
|
+
/** @type {Block} */
|
|
11
|
+
#block;
|
|
12
|
+
|
|
13
|
+
/** @param {any[]} params */
|
|
14
|
+
constructor(...params) {
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
super(...params);
|
|
17
|
+
|
|
18
|
+
var block = this.#block = safe_scope();
|
|
19
|
+
this.#time = tracked(super.getTime(), block);
|
|
20
|
+
|
|
21
|
+
if (!init) this.#init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#init() {
|
|
25
|
+
init = true;
|
|
26
|
+
|
|
27
|
+
var proto = TrackedDate.prototype;
|
|
28
|
+
var date_proto = Date.prototype;
|
|
29
|
+
|
|
30
|
+
var methods = /** @type {Array<keyof Date & string>} */ (
|
|
31
|
+
Object.getOwnPropertyNames(date_proto)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
for (const method of methods) {
|
|
35
|
+
if (method.startsWith('get') || method.startsWith('to') || method === 'valueOf') {
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
proto[method] = function (...args) {
|
|
38
|
+
// don't memoize if there are arguments
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
if (args.length > 0) {
|
|
41
|
+
get(this.#time);
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
return date_proto[method].apply(this, args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var d = this.#deriveds.get(method);
|
|
47
|
+
|
|
48
|
+
if (d === undefined) {
|
|
49
|
+
d = derived(() => {
|
|
50
|
+
get(this.#time);
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
return date_proto[method].apply(this, args);
|
|
53
|
+
}, this.#block);
|
|
54
|
+
|
|
55
|
+
this.#deriveds.set(method, d);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return get(d);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (method.startsWith('set')) {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
proto[method] = function (...args) {
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
var result = date_proto[method].apply(this, args);
|
|
67
|
+
set(this.#time, date_proto.getTime.call(this), this.#block);
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -15,38 +15,38 @@ export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
|
|
|
15
15
|
* @returns {() => void}
|
|
16
16
|
*/
|
|
17
17
|
export function mount(component, options) {
|
|
18
|
-
|
|
18
|
+
init_operations();
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const props = options.props || {};
|
|
21
|
+
const target = options.target;
|
|
22
|
+
const anchor = create_anchor();
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// Clear target content in case of SSR
|
|
25
|
+
if (target.firstChild) {
|
|
26
|
+
target.textContent = '';
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
target.append(anchor);
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const cleanup_events = handle_root_events(target);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const _root = root(() => {
|
|
34
|
+
component(anchor, props, active_block);
|
|
35
|
+
});
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
return () => {
|
|
38
|
+
cleanup_events();
|
|
39
|
+
destroy_block(_root);
|
|
40
|
+
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export { create_context as createContext } from './internal/client/context.js';
|
|
44
44
|
|
|
45
45
|
export {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
flush_sync as flushSync,
|
|
47
|
+
track,
|
|
48
|
+
track_split as trackSplit,
|
|
49
|
+
untrack,
|
|
50
50
|
} from './internal/client/runtime.js';
|
|
51
51
|
|
|
52
52
|
export { TrackedArray } from './array.js';
|
|
@@ -57,6 +57,8 @@ export { TrackedSet } from './set.js';
|
|
|
57
57
|
|
|
58
58
|
export { TrackedMap } from './map.js';
|
|
59
59
|
|
|
60
|
+
export { TrackedDate } from './date.js';
|
|
61
|
+
|
|
60
62
|
export { keyed } from './internal/client/for.js';
|
|
61
63
|
|
|
62
64
|
export { user_effect as effect } from './internal/client/blocks.js';
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, TrackedDate, track } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('TrackedDate', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('handles getTime() with reactive updates', () => {
|
|
24
|
+
component DateTest() {
|
|
25
|
+
let date = new TrackedDate(2025, 0, 1);
|
|
26
|
+
let time = track(() => date.getTime());
|
|
27
|
+
|
|
28
|
+
<button onClick={() => date.setFullYear(2026)}>{'Change Year'}</button>
|
|
29
|
+
<pre>{@time}</pre>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(DateTest);
|
|
33
|
+
|
|
34
|
+
const button = container.querySelector('button');
|
|
35
|
+
const initialTime = container.querySelector('pre').textContent;
|
|
36
|
+
|
|
37
|
+
button.click();
|
|
38
|
+
flushSync();
|
|
39
|
+
|
|
40
|
+
const newTime = container.querySelector('pre').textContent;
|
|
41
|
+
expect(newTime).not.toBe(initialTime);
|
|
42
|
+
expect(parseInt(newTime)).toBeGreaterThan(parseInt(initialTime));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('handles getFullYear() with reactive updates', () => {
|
|
46
|
+
component DateTest() {
|
|
47
|
+
let date = new TrackedDate(2025, 5, 15);
|
|
48
|
+
let year = track(() => date.getFullYear());
|
|
49
|
+
|
|
50
|
+
<button onClick={() => date.setFullYear(2030)}>{'Change Year'}</button>
|
|
51
|
+
<pre>{@year}</pre>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render(DateTest);
|
|
55
|
+
|
|
56
|
+
const button = container.querySelector('button');
|
|
57
|
+
|
|
58
|
+
expect(container.querySelector('pre').textContent).toBe('2025');
|
|
59
|
+
|
|
60
|
+
button.click();
|
|
61
|
+
flushSync();
|
|
62
|
+
|
|
63
|
+
expect(container.querySelector('pre').textContent).toBe('2030');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('handles getMonth() with reactive updates', () => {
|
|
67
|
+
component DateTest() {
|
|
68
|
+
let date = new TrackedDate(2025, 0, 15);
|
|
69
|
+
let month = track(() => date.getMonth());
|
|
70
|
+
|
|
71
|
+
<button onClick={() => date.setMonth(11)}>{'Change to December'}</button>
|
|
72
|
+
<pre>{@month}</pre>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render(DateTest);
|
|
76
|
+
|
|
77
|
+
const button = container.querySelector('button');
|
|
78
|
+
|
|
79
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
80
|
+
|
|
81
|
+
button.click();
|
|
82
|
+
flushSync();
|
|
83
|
+
|
|
84
|
+
expect(container.querySelector('pre').textContent).toBe('11');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('handles getDate() with reactive updates', () => {
|
|
88
|
+
component DateTest() {
|
|
89
|
+
let date = new TrackedDate(2025, 0, 1);
|
|
90
|
+
let day = track(() => date.getDate());
|
|
91
|
+
|
|
92
|
+
<button onClick={() => date.setDate(15)}>{'Change Day'}</button>
|
|
93
|
+
<pre>{@day}</pre>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render(DateTest);
|
|
97
|
+
|
|
98
|
+
const button = container.querySelector('button');
|
|
99
|
+
|
|
100
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
101
|
+
|
|
102
|
+
button.click();
|
|
103
|
+
flushSync();
|
|
104
|
+
|
|
105
|
+
expect(container.querySelector('pre').textContent).toBe('15');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('handles getDay() with reactive updates', () => {
|
|
109
|
+
component DateTest() {
|
|
110
|
+
let date = new TrackedDate(2025, 0, 1);
|
|
111
|
+
let dayOfWeek = track(() => date.getDay());
|
|
112
|
+
|
|
113
|
+
<button onClick={() => date.setDate(2)}>{'Next Day'}</button>
|
|
114
|
+
<pre>{@dayOfWeek}</pre>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
render(DateTest);
|
|
118
|
+
|
|
119
|
+
const button = container.querySelector('button');
|
|
120
|
+
|
|
121
|
+
expect(container.querySelector('pre').textContent).toBe('3');
|
|
122
|
+
|
|
123
|
+
button.click();
|
|
124
|
+
flushSync();
|
|
125
|
+
|
|
126
|
+
expect(container.querySelector('pre').textContent).toBe('4');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles getHours() with reactive updates', () => {
|
|
130
|
+
component DateTest() {
|
|
131
|
+
let date = new TrackedDate(2025, 0, 1, 10, 30, 0);
|
|
132
|
+
let hours = track(() => date.getHours());
|
|
133
|
+
|
|
134
|
+
<button onClick={() => date.setHours(15)}>{'Change to 3 PM'}</button>
|
|
135
|
+
<pre>{@hours}</pre>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
render(DateTest);
|
|
139
|
+
|
|
140
|
+
const button = container.querySelector('button');
|
|
141
|
+
|
|
142
|
+
expect(container.querySelector('pre').textContent).toBe('10');
|
|
143
|
+
|
|
144
|
+
button.click();
|
|
145
|
+
flushSync();
|
|
146
|
+
|
|
147
|
+
expect(container.querySelector('pre').textContent).toBe('15');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('handles getMinutes() with reactive updates', () => {
|
|
151
|
+
component DateTest() {
|
|
152
|
+
let date = new TrackedDate(2025, 0, 1, 10, 15, 0);
|
|
153
|
+
let minutes = track(() => date.getMinutes());
|
|
154
|
+
|
|
155
|
+
<button onClick={() => date.setMinutes(45)}>{'Change Minutes'}</button>
|
|
156
|
+
<pre>{@minutes}</pre>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render(DateTest);
|
|
160
|
+
|
|
161
|
+
const button = container.querySelector('button');
|
|
162
|
+
|
|
163
|
+
expect(container.querySelector('pre').textContent).toBe('15');
|
|
164
|
+
|
|
165
|
+
button.click();
|
|
166
|
+
flushSync();
|
|
167
|
+
|
|
168
|
+
expect(container.querySelector('pre').textContent).toBe('45');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('handles getSeconds() with reactive updates', () => {
|
|
172
|
+
component DateTest() {
|
|
173
|
+
let date = new TrackedDate(2025, 0, 1, 10, 15, 30);
|
|
174
|
+
let seconds = track(() => date.getSeconds());
|
|
175
|
+
|
|
176
|
+
<button onClick={() => date.setSeconds(45)}>{'Change Seconds'}</button>
|
|
177
|
+
<pre>{@seconds}</pre>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
render(DateTest);
|
|
181
|
+
|
|
182
|
+
const button = container.querySelector('button');
|
|
183
|
+
|
|
184
|
+
expect(container.querySelector('pre').textContent).toBe('30');
|
|
185
|
+
|
|
186
|
+
button.click();
|
|
187
|
+
flushSync();
|
|
188
|
+
|
|
189
|
+
expect(container.querySelector('pre').textContent).toBe('45');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('handles toISOString() with reactive updates', () => {
|
|
193
|
+
component DateTest() {
|
|
194
|
+
let date = new TrackedDate(2025, 0, 1, 12, 0, 0);
|
|
195
|
+
let isoString = track(() => date.toISOString());
|
|
196
|
+
|
|
197
|
+
<button onClick={() => date.setFullYear(2026)}>{'Change Year'}</button>
|
|
198
|
+
<pre>{@isoString}</pre>
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
render(DateTest);
|
|
202
|
+
|
|
203
|
+
const button = container.querySelector('button');
|
|
204
|
+
const initialISO = container.querySelector('pre').textContent;
|
|
205
|
+
|
|
206
|
+
expect(initialISO).toContain('2025');
|
|
207
|
+
|
|
208
|
+
button.click();
|
|
209
|
+
flushSync();
|
|
210
|
+
|
|
211
|
+
const newISO = container.querySelector('pre').textContent;
|
|
212
|
+
|
|
213
|
+
// Just verify that the ISO string changed after the year was updated
|
|
214
|
+
expect(newISO).not.toBe(initialISO);
|
|
215
|
+
expect(newISO.length).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('handles toDateString() with reactive updates', () => {
|
|
219
|
+
component DateTest() {
|
|
220
|
+
let date = new TrackedDate(2025, 0, 1);
|
|
221
|
+
let dateString = track(() => date.toDateString());
|
|
222
|
+
|
|
223
|
+
<button onClick={() => date.setMonth(11)}>{'Change to December'}</button>
|
|
224
|
+
<pre>{@dateString}</pre>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
render(DateTest);
|
|
228
|
+
|
|
229
|
+
const button = container.querySelector('button');
|
|
230
|
+
const initialDateString = container.querySelector('pre').textContent;
|
|
231
|
+
|
|
232
|
+
expect(initialDateString).toContain('Jan');
|
|
233
|
+
|
|
234
|
+
button.click();
|
|
235
|
+
flushSync();
|
|
236
|
+
|
|
237
|
+
const newDateString = container.querySelector('pre').textContent;
|
|
238
|
+
expect(newDateString).toContain('Dec');
|
|
239
|
+
expect(newDateString).not.toBe(initialDateString);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('handles valueOf() with reactive updates', () => {
|
|
243
|
+
component DateTest() {
|
|
244
|
+
let date = new TrackedDate(2025, 0, 1);
|
|
245
|
+
let valueOf = track(() => date.valueOf());
|
|
246
|
+
|
|
247
|
+
<button onClick={() => date.setDate(2)}>{'Next Day'}</button>
|
|
248
|
+
<pre>{@valueOf}</pre>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
render(DateTest);
|
|
252
|
+
|
|
253
|
+
const button = container.querySelector('button');
|
|
254
|
+
const initialValue = parseInt(container.querySelector('pre').textContent);
|
|
255
|
+
|
|
256
|
+
button.click();
|
|
257
|
+
flushSync();
|
|
258
|
+
|
|
259
|
+
const newValue = parseInt(container.querySelector('pre').textContent);
|
|
260
|
+
expect(newValue).toBeGreaterThan(initialValue);
|
|
261
|
+
expect(newValue - initialValue).toBe(24 * 60 * 60 * 1000);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('handles multiple get methods reacting to same setTime change', () => {
|
|
265
|
+
component DateTest() {
|
|
266
|
+
let date = new TrackedDate(2025, 0, 1, 10, 30, 15);
|
|
267
|
+
let year = track(() => date.getFullYear());
|
|
268
|
+
let month = track(() => date.getMonth());
|
|
269
|
+
let day = track(() => date.getDate());
|
|
270
|
+
let hours = track(() => date.getHours());
|
|
271
|
+
|
|
272
|
+
<button onClick={() => date.setTime(new Date(2026, 5, 15, 14, 45, 30).getTime())}>{'Change All'}</button>
|
|
273
|
+
<div>
|
|
274
|
+
{'Year: '}
|
|
275
|
+
{@year}
|
|
276
|
+
</div>
|
|
277
|
+
<div>
|
|
278
|
+
{'Month: '}
|
|
279
|
+
{@month}
|
|
280
|
+
</div>
|
|
281
|
+
<div>
|
|
282
|
+
{'Day: '}
|
|
283
|
+
{@day}
|
|
284
|
+
</div>
|
|
285
|
+
<div>
|
|
286
|
+
{'Hours: '}
|
|
287
|
+
{@hours}
|
|
288
|
+
</div>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
render(DateTest);
|
|
292
|
+
|
|
293
|
+
const button = container.querySelector('button');
|
|
294
|
+
const divs = container.querySelectorAll('div');
|
|
295
|
+
|
|
296
|
+
expect(divs[0].textContent).toBe('Year: 2025');
|
|
297
|
+
expect(divs[1].textContent).toBe('Month: 0');
|
|
298
|
+
expect(divs[2].textContent).toBe('Day: 1');
|
|
299
|
+
expect(divs[3].textContent).toBe('Hours: 10');
|
|
300
|
+
|
|
301
|
+
button.click();
|
|
302
|
+
flushSync();
|
|
303
|
+
|
|
304
|
+
expect(divs[0].textContent).toBe('Year: 2026');
|
|
305
|
+
expect(divs[1].textContent).toBe('Month: 5');
|
|
306
|
+
expect(divs[2].textContent).toBe('Day: 15');
|
|
307
|
+
expect(divs[3].textContent).toBe('Hours: 14');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('handles constructor with different parameter combinations', () => {
|
|
311
|
+
component DateTest() {
|
|
312
|
+
let dateNow = new TrackedDate();
|
|
313
|
+
let dateFromString = new TrackedDate('2025-01-01');
|
|
314
|
+
let dateFromNumbers = new TrackedDate(2025, 0, 1);
|
|
315
|
+
let dateFromTimestamp = new TrackedDate(1735689600000);
|
|
316
|
+
|
|
317
|
+
let nowYear = track(() => dateNow.getFullYear());
|
|
318
|
+
let stringYear = track(() => dateFromString.getFullYear());
|
|
319
|
+
let numbersYear = track(() => dateFromNumbers.getFullYear());
|
|
320
|
+
let timestampYear = track(() => dateFromTimestamp.getFullYear());
|
|
321
|
+
|
|
322
|
+
<div>
|
|
323
|
+
{'Now: '}
|
|
324
|
+
{@nowYear}
|
|
325
|
+
</div>
|
|
326
|
+
<div>
|
|
327
|
+
{'String: '}
|
|
328
|
+
{@stringYear}
|
|
329
|
+
</div>
|
|
330
|
+
<div>
|
|
331
|
+
{'Numbers: '}
|
|
332
|
+
{@numbersYear}
|
|
333
|
+
</div>
|
|
334
|
+
<div>
|
|
335
|
+
{'Timestamp: '}
|
|
336
|
+
{@timestampYear}
|
|
337
|
+
</div>
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
render(DateTest);
|
|
341
|
+
|
|
342
|
+
const divs = container.querySelectorAll('div');
|
|
343
|
+
const currentYear = new Date().getFullYear();
|
|
344
|
+
|
|
345
|
+
expect(parseInt(divs[0].textContent.split(': ')[1])).toBe(currentYear);
|
|
346
|
+
|
|
347
|
+
// String date parsing may vary by timezone, just check it's a reasonable year
|
|
348
|
+
const stringYear = parseInt(divs[1].textContent.split(': ')[1]);
|
|
349
|
+
expect(stringYear).toBeGreaterThanOrEqual(2024);
|
|
350
|
+
expect(stringYear).toBeLessThanOrEqual(2025);
|
|
351
|
+
expect(divs[2].textContent).toBe('Numbers: 2025');
|
|
352
|
+
|
|
353
|
+
// Timestamp parsing may also vary by timezone
|
|
354
|
+
const timestampYear = parseInt(divs[3].textContent.split(': ')[1]);
|
|
355
|
+
expect(timestampYear).toBeGreaterThanOrEqual(2024);
|
|
356
|
+
expect(timestampYear).toBeLessThanOrEqual(2025);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('handles get methods with arguments non-memoized', () => {
|
|
360
|
+
component DateTest() {
|
|
361
|
+
let date = new TrackedDate();
|
|
362
|
+
let localeDateString = track(() => date.toLocaleDateString('en-US'));
|
|
363
|
+
let localeTimeString = track(() => date.toLocaleTimeString('en-US'));
|
|
364
|
+
|
|
365
|
+
<button onClick={() => date.setFullYear(date.getFullYear() + 1)}>{'Next Year'}</button>
|
|
366
|
+
<div>
|
|
367
|
+
{'Date: '}
|
|
368
|
+
{@localeDateString}
|
|
369
|
+
</div>
|
|
370
|
+
<div>
|
|
371
|
+
{'Time: '}
|
|
372
|
+
{@localeTimeString}
|
|
373
|
+
</div>
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
render(DateTest);
|
|
377
|
+
|
|
378
|
+
const button = container.querySelector('button');
|
|
379
|
+
const divs = container.querySelectorAll('div');
|
|
380
|
+
const initialDate = divs[0].textContent;
|
|
381
|
+
const initialTime = divs[1].textContent;
|
|
382
|
+
|
|
383
|
+
button.click();
|
|
384
|
+
flushSync();
|
|
385
|
+
|
|
386
|
+
const newDate = divs[0].textContent;
|
|
387
|
+
const newTime = divs[1].textContent;
|
|
388
|
+
|
|
389
|
+
expect(newDate).not.toBe(initialDate);
|
|
390
|
+
expect(newTime).toBe(initialTime);
|
|
391
|
+
});
|
|
392
|
+
});
|
package/types/index.d.ts
CHANGED
|
@@ -12,13 +12,13 @@ export declare function flushSync<T>(fn: () => T): T;
|
|
|
12
12
|
export declare function effect(fn: (() => void) | (() => () => void)): void;
|
|
13
13
|
|
|
14
14
|
export interface TrackedArrayConstructor {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
new <T>(...elements: T[]): TrackedArray<T>; // must be used with `new`
|
|
16
|
+
from<T>(arrayLike: ArrayLike<T>): TrackedArray<T>;
|
|
17
|
+
of<T>(...items: T[]): TrackedArray<T>;
|
|
18
|
+
fromAsync<T>(iterable: AsyncIterable<T>): Promise<TrackedArray<T>>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export interface TrackedArray<T> extends Array<T> {}
|
|
21
|
+
export interface TrackedArray<T> extends Array<T> { }
|
|
22
22
|
|
|
23
23
|
export declare const TrackedArray: TrackedArrayConstructor;
|
|
24
24
|
|
|
@@ -38,10 +38,12 @@ export declare class TrackedSet<T> extends Set<T> {
|
|
|
38
38
|
symmetricDifference(other: TrackedSet<T> | Set<T>): TrackedSet<T>;
|
|
39
39
|
union(other: TrackedSet<T> | Set<T>): TrackedSet<T>;
|
|
40
40
|
toJSON(): T[];
|
|
41
|
+
#private;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export declare class TrackedMap<K, V> extends Map<K, V> {
|
|
44
45
|
toJSON(): [K, V][];
|
|
46
|
+
#private;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// Compiler-injected runtime symbols (for Ripple component development)
|
|
@@ -74,17 +76,17 @@ export type Tracked<V> = { '#v': V };
|
|
|
74
76
|
export type Props<K extends PropertyKey = any, V = unknown> = Record<K, V>;
|
|
75
77
|
export type PropsWithExtras<T extends object> = Props & T & Record<string, unknown>;
|
|
76
78
|
export type PropsWithChildren<T extends object = {}> =
|
|
77
|
-
|
|
79
|
+
Expand<Omit<Props, 'children'> & { children: Component } & T>;
|
|
78
80
|
|
|
79
81
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
80
82
|
|
|
81
83
|
type PickKeys<T, K extends readonly (keyof T)[]> =
|
|
82
|
-
|
|
84
|
+
{ [I in keyof K]: Tracked<T[K[I] & keyof T]> };
|
|
83
85
|
|
|
84
86
|
type RestKeys<T, K extends readonly (keyof T)[]> = Expand<Omit<T, K[number]>>;
|
|
85
87
|
|
|
86
88
|
type SplitResult<T extends Props, K extends readonly (keyof T)[]> =
|
|
87
|
-
|
|
89
|
+
[...PickKeys<T, K>, Tracked<RestKeys<T, K>>];
|
|
88
90
|
|
|
89
91
|
export declare function track<V>(value?: V | (() => V), get?: (v: V) => V, set?: (next: V, prev: V) => V): Tracked<V>;
|
|
90
92
|
|
|
@@ -129,33 +131,38 @@ export function on(
|
|
|
129
131
|
): () => void;
|
|
130
132
|
|
|
131
133
|
export type TrackedObjectShallow<T> = {
|
|
132
|
-
|
|
134
|
+
[K in keyof T]: T[K] | Tracked<T[K]>;
|
|
133
135
|
};
|
|
134
136
|
|
|
135
137
|
export type TrackedObjectDeep<T> =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
T extends string | number | boolean | null | undefined | symbol | bigint
|
|
139
|
+
? T | Tracked<T>
|
|
140
|
+
: T extends TrackedArray<infer U>
|
|
141
|
+
? TrackedArray<U> | Tracked<TrackedArray<U>>
|
|
142
|
+
: T extends TrackedSet<infer U>
|
|
143
|
+
? TrackedSet<U> | Tracked<TrackedSet<U>>
|
|
144
|
+
: T extends TrackedMap<infer K, infer V>
|
|
145
|
+
? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
|
|
146
|
+
: T extends Array<infer U>
|
|
147
|
+
? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
|
|
148
|
+
: T extends Set<infer U>
|
|
149
|
+
? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
|
|
150
|
+
: T extends Map<infer K, infer V>
|
|
151
|
+
? Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>> |
|
|
152
|
+
Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
|
|
153
|
+
: T extends object
|
|
154
|
+
? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
|
|
155
|
+
: T | Tracked<T>;
|
|
154
156
|
|
|
155
157
|
export type TrackedObject<T extends object> = T & {};
|
|
156
158
|
|
|
157
159
|
export interface TrackedObjectConstructor {
|
|
158
|
-
|
|
160
|
+
new <T extends object>(obj: T): TrackedObject<T>;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
export declare const TrackedObject: TrackedObjectConstructor;
|
|
164
|
+
|
|
165
|
+
export class SvelteDate extends Date {
|
|
166
|
+
constructor(...params: any[]);
|
|
167
|
+
#private;
|
|
168
|
+
}
|