ripple 0.2.113 → 0.2.115

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": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.113",
6
+ "version": "0.2.115",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -38,11 +38,6 @@
38
38
  "require": "./src/compiler/index.js",
39
39
  "default": "./src/compiler/index.js"
40
40
  },
41
- "./bindings": {
42
- "types": "./src/bindings/index.d.ts",
43
- "require": "./src/bindings/index.js",
44
- "default": "./src/bindings/index.js"
45
- },
46
41
  "./validator": {
47
42
  "types": "./types/index.d.ts",
48
43
  "require": "./validator/index.js",
@@ -1306,6 +1306,7 @@ function RipplePlugin(config) {
1306
1306
  }
1307
1307
 
1308
1308
  if (this.value === 'component') {
1309
+ this.awaitPos = 0;
1309
1310
  const node = this.startNode();
1310
1311
  node.type = 'Component';
1311
1312
  node.css = null;
@@ -567,6 +567,23 @@ const visitors = {
567
567
 
568
568
  mark_control_flow_has_template(path);
569
569
 
570
+ // Store capitalized name for dynamic components/elements
571
+ if (node.id.tracked) {
572
+ const original_name = node.id.name;
573
+ const capitalized_name = original_name.charAt(0).toUpperCase() + original_name.slice(1);
574
+ node.metadata.ts_name = capitalized_name;
575
+ node.metadata.original_name = original_name;
576
+
577
+ // Mark the binding as a dynamic component so we can capitalize it everywhere
578
+ const binding = context.state.scope.get(original_name);
579
+ if (binding) {
580
+ if (!binding.metadata) {
581
+ binding.metadata = {};
582
+ }
583
+ binding.metadata.is_dynamic_component = true;
584
+ }
585
+ }
586
+
570
587
  if (is_dom_element) {
571
588
  if (node.id.name === 'head') {
572
589
  // head validation
@@ -161,6 +161,15 @@ const visitors = {
161
161
  if (is_reference(node, parent)) {
162
162
  if (context.state.to_ts) {
163
163
  if (node.tracked) {
164
+ // Check if this identifier is used as a dynamic component/element
165
+ // by checking if it has a capitalized name in metadata
166
+ const binding = context.state.scope.get(node.name);
167
+ if (binding?.metadata?.is_dynamic_component) {
168
+ // Capitalize the identifier for TypeScript
169
+ const capitalizedName = node.name.charAt(0).toUpperCase() + node.name.slice(1);
170
+ const capitalizedNode = { ...node, name: capitalizedName };
171
+ return b.member(capitalizedNode, b.literal('#v'), true);
172
+ }
164
173
  return b.member(node, b.literal('#v'), true);
165
174
  }
166
175
  } else {
@@ -204,7 +213,7 @@ const visitors = {
204
213
  return {
205
214
  ...node,
206
215
  specifiers: node.specifiers
207
- .filter((spec) => spec.importKind !== 'type')
216
+ .filter((spec) => context.state.to_ts || spec.importKind !== 'type')
208
217
  .map((spec) => context.visit(spec)),
209
218
  };
210
219
  },
@@ -441,6 +450,22 @@ const visitors = {
441
450
  return context.next();
442
451
  },
443
452
 
453
+ VariableDeclarator(node, context) {
454
+ // In TypeScript mode, capitalize identifiers that are used as dynamic components
455
+ if (context.state.to_ts && node.id.type === 'Identifier') {
456
+ const binding = context.state.scope.get(node.id.name);
457
+ if (binding?.metadata?.is_dynamic_component) {
458
+ const capitalizedName = node.id.name.charAt(0).toUpperCase() + node.id.name.slice(1);
459
+ return {
460
+ ...node,
461
+ id: { ...node.id, name: capitalizedName },
462
+ init: node.init ? context.visit(node.init) : null
463
+ };
464
+ }
465
+ }
466
+ return context.next();
467
+ },
468
+
444
469
  FunctionDeclaration(node, context) {
445
470
  return visit_function(node, context);
446
471
  },
@@ -1397,7 +1422,8 @@ function transform_ts_child(node, context) {
1397
1422
  // Do we need to do something special here?
1398
1423
  state.init.push(b.stmt(visit(node.expression, { ...state })));
1399
1424
  } else if (node.type === 'Element') {
1400
- const type = node.id.name;
1425
+ // Use capitalized name for dynamic components/elements in TypeScript output
1426
+ const type = node.metadata?.ts_name || node.id.name;
1401
1427
  const children = [];
1402
1428
  let has_children_props = false;
1403
1429
 
@@ -1463,7 +1489,17 @@ function transform_ts_child(node, context) {
1463
1489
  }
1464
1490
 
1465
1491
  const opening_type = b.jsx_id(type);
1466
- opening_type.loc = node.id.loc;
1492
+ // Use node.id.loc if available, otherwise create a loc based on the element's position
1493
+ opening_type.loc = node.id.loc || {
1494
+ start: {
1495
+ line: node.loc.start.line,
1496
+ column: node.loc.start.column + 2, // After "<@"
1497
+ },
1498
+ end: {
1499
+ line: node.loc.start.line,
1500
+ column: node.loc.start.column + 2 + type.length,
1501
+ },
1502
+ };
1467
1503
 
1468
1504
  let closing_type = undefined;
1469
1505
 
@@ -1481,9 +1517,15 @@ function transform_ts_child(node, context) {
1481
1517
  };
1482
1518
  }
1483
1519
 
1484
- state.init.push(
1485
- b.stmt(b.jsx_element(opening_type, attributes, children, node.selfClosing, closing_type)),
1486
- );
1520
+ const jsxElement = b.jsx_element(opening_type, attributes, children, node.selfClosing, closing_type);
1521
+ // Preserve metadata from Element node for mapping purposes
1522
+ if (node.metadata && (node.metadata.ts_name || node.metadata.original_name)) {
1523
+ jsxElement.metadata = {
1524
+ ts_name: node.metadata.ts_name,
1525
+ original_name: node.metadata.original_name
1526
+ };
1527
+ }
1528
+ state.init.push(b.stmt(jsxElement));
1487
1529
  } else if (node.type === 'IfStatement') {
1488
1530
  const consequent_scope = context.state.scopes.get(node.consequent);
1489
1531
  const consequent = b.block(
@@ -5,6 +5,14 @@ export const mapping_data = {
5
5
  completion: true,
6
6
  semantic: true,
7
7
  navigation: true,
8
+ rename: true,
9
+ codeActions: false, // set to false to disable auto import when importing yourself
10
+ formatting: false, // not doing formatting through Volar, using Prettier.
11
+ // these 3 below will be true by default
12
+ // leaving for reference
13
+ // hover: true,
14
+ // definition: true,
15
+ // references: true,
8
16
  };
9
17
 
10
18
  /**
@@ -22,6 +30,26 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
22
30
  let sourceIndex = 0;
23
31
  let generatedIndex = 0;
24
32
 
33
+ // Map to track capitalized names: original name -> capitalized name
34
+ /** @type {Map<string, string>} */
35
+ const capitalizedNames = new Map();
36
+ // Reverse map: capitalized name -> original name
37
+ /** @type {Map<string, string>} */
38
+ const reverseCapitalizedNames = new Map();
39
+
40
+ // Pre-walk to collect capitalized names from JSXElement nodes (transformed AST)
41
+ // These are identifiers that are used as dynamic components/elements
42
+ walk(ast, null, {
43
+ _(node, { next }) {
44
+ // Check JSXElement nodes with metadata (preserved from Element nodes)
45
+ if (node.type === 'JSXElement' && node.metadata?.ts_name && node.metadata?.original_name) {
46
+ capitalizedNames.set(node.metadata.original_name, node.metadata.ts_name);
47
+ reverseCapitalizedNames.set(node.metadata.ts_name, node.metadata.original_name);
48
+ }
49
+ next();
50
+ }
51
+ });
52
+
25
53
  /**
26
54
  * Check if character is a word boundary (not alphanumeric or underscore)
27
55
  * @param {string} char
@@ -31,6 +59,29 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
31
59
  return char === undefined || !/[a-zA-Z0-9_$]/.test(char);
32
60
  };
33
61
 
62
+ /**
63
+ * Check if a position is inside a comment
64
+ * @param {number} pos - Position to check
65
+ * @returns {boolean}
66
+ */
67
+ const isInComment = (pos) => {
68
+ // Check for single-line comment: find start of line and check if there's // before this position
69
+ let lineStart = source.lastIndexOf('\n', pos - 1) + 1;
70
+ const lineBeforePos = source.substring(lineStart, pos);
71
+ if (lineBeforePos.includes('//')) {
72
+ return true;
73
+ }
74
+ // Check for multi-line comment: look backwards for /* and forwards for */
75
+ const lastCommentStart = source.lastIndexOf('/*', pos);
76
+ if (lastCommentStart !== -1) {
77
+ const commentEnd = source.indexOf('*/', lastCommentStart);
78
+ if (commentEnd === -1 || commentEnd > pos) {
79
+ return true; // We're inside an unclosed or open comment
80
+ }
81
+ }
82
+ return false;
83
+ };
84
+
34
85
  /**
35
86
  * Find text in source string, searching character by character from sourceIndex
36
87
  * @param {string} text - Text to find
@@ -46,6 +97,11 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
46
97
  }
47
98
  }
48
99
  if (match) {
100
+ // Skip if this match is inside a comment
101
+ if (isInComment(i)) {
102
+ continue;
103
+ }
104
+
49
105
  // Check word boundaries for identifier-like tokens
50
106
  const isIdentifierLike = /^[a-zA-Z_$]/.test(text);
51
107
  if (isIdentifierLike) {
@@ -96,30 +152,73 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
96
152
  };
97
153
 
98
154
  // Collect text tokens from AST nodes
99
- /** @type {string[]} */
155
+ // Tokens can be either strings or objects with source/generated properties
156
+ /** @type {Array<string | {source: string, generated: string}>} */
100
157
  const tokens = [];
101
158
 
159
+ // Collect import declarations for full-statement mappings
160
+ /** @type {Array<{start: number, end: number}>} */
161
+ const importDeclarations = [];
162
+
102
163
  // We have to visit everything in generated order to maintain correct indices
103
164
  walk(ast, null, {
104
165
  _(node, { visit }) {
105
166
  // Collect key node types: Identifiers, Literals, and JSX Elements
167
+ // Only collect tokens from nodes with .loc (skip synthesized nodes like children attribute)
106
168
  if (node.type === 'Identifier' && node.name) {
107
- tokens.push(node.name);
169
+ if (node.loc) {
170
+ // Check if this identifier was capitalized (reverse lookup)
171
+ const originalName = reverseCapitalizedNames.get(node.name);
172
+ if (originalName) {
173
+ // This is a capitalized name in generated code, map to lowercase in source
174
+ tokens.push({ source: originalName, generated: node.name });
175
+ } else {
176
+ // Check if this identifier should be capitalized (forward lookup)
177
+ const capitalizedName = capitalizedNames.get(node.name);
178
+ if (capitalizedName) {
179
+ tokens.push({ source: node.name, generated: capitalizedName });
180
+ } else {
181
+ tokens.push(node.name);
182
+ }
183
+ }
184
+ }
108
185
  return; // Leaf node, don't traverse further
109
186
  } else if (node.type === 'JSXIdentifier' && node.name) {
110
- tokens.push(node.name);
187
+ if (node.loc) {
188
+ // Check if this was capitalized (reverse lookup)
189
+ const originalName = reverseCapitalizedNames.get(node.name);
190
+ if (originalName) {
191
+ tokens.push({ source: originalName, generated: node.name });
192
+ } else {
193
+ // Check if this should be capitalized (forward lookup)
194
+ const capitalizedName = capitalizedNames.get(node.name);
195
+ if (capitalizedName) {
196
+ tokens.push({ source: node.name, generated: capitalizedName });
197
+ } else {
198
+ tokens.push(node.name);
199
+ }
200
+ }
201
+ }
111
202
  return; // Leaf node, don't traverse further
112
203
  } else if (node.type === 'Literal' && node.raw) {
113
- tokens.push(node.raw);
204
+ if (node.loc) {
205
+ tokens.push(node.raw);
206
+ }
114
207
  return; // Leaf node, don't traverse further
115
208
  } else if (node.type === 'ImportDeclaration') {
209
+ // Collect import declaration range for full-statement mapping
210
+ // TypeScript reports unused imports with diagnostics covering the entire statement
211
+ if (node.start !== undefined && node.end !== undefined) {
212
+ importDeclarations.push({ start: node.start, end: node.end });
213
+ }
214
+
116
215
  // Visit specifiers in source order
117
216
  if (node.specifiers) {
118
217
  for (const specifier of node.specifiers) {
119
218
  visit(specifier);
120
219
  }
121
220
  }
122
- // Skip source (just a string literal)
221
+ visit(node.source);
123
222
  return;
124
223
  } else if (node.type === 'ImportSpecifier') {
125
224
  // If local and imported are the same, only visit local to avoid duplicates
@@ -209,7 +308,20 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
209
308
 
210
309
  // 3. Push closing tag name (not visited by AST walker)
211
310
  if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
212
- tokens.push(node.closingElement.name.name);
311
+ const closingName = node.closingElement.name.name;
312
+ // Check if this was capitalized (reverse lookup)
313
+ const originalName = reverseCapitalizedNames.get(closingName);
314
+ if (originalName) {
315
+ tokens.push({ source: originalName, generated: closingName });
316
+ } else {
317
+ // Check if this should be capitalized (forward lookup)
318
+ const capitalizedName = capitalizedNames.get(closingName);
319
+ if (capitalizedName) {
320
+ tokens.push({ source: closingName, generated: capitalizedName });
321
+ } else {
322
+ tokens.push(closingName);
323
+ }
324
+ }
213
325
  }
214
326
 
215
327
  return;
@@ -988,23 +1100,65 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
988
1100
  });
989
1101
 
990
1102
  // Process each token in order
991
- for (const text of tokens) {
992
- const sourcePos = findInSource(text);
993
- const genPos = findInGenerated(text);
1103
+ for (const token of tokens) {
1104
+ let sourceText, generatedText;
1105
+
1106
+ if (typeof token === 'string') {
1107
+ sourceText = token;
1108
+ generatedText = token;
1109
+ } else {
1110
+ // Token with different source and generated names
1111
+ sourceText = token.source;
1112
+ generatedText = token.generated;
1113
+ }
1114
+
1115
+ const sourcePos = findInSource(sourceText);
1116
+ const genPos = findInGenerated(generatedText);
994
1117
 
995
1118
  if (sourcePos !== null && genPos !== null) {
996
1119
  mappings.push({
997
1120
  sourceOffsets: [sourcePos],
998
1121
  generatedOffsets: [genPos],
999
- lengths: [text.length],
1122
+ lengths: [sourceText.length],
1000
1123
  data: mapping_data,
1001
1124
  });
1002
1125
  }
1003
1126
  }
1004
1127
 
1128
+ // Add full-statement mappings for import declarations
1129
+ // TypeScript reports unused import diagnostics covering the entire import statement
1130
+ // Use verification-only mapping to avoid duplicate hover/completion
1131
+ for (const importDecl of importDeclarations) {
1132
+ const length = importDecl.end - importDecl.start;
1133
+ mappings.push({
1134
+ sourceOffsets: [importDecl.start],
1135
+ generatedOffsets: [importDecl.start], // Same position in generated code
1136
+ lengths: [length],
1137
+ data: {
1138
+ // only verification (diagnostics) to avoid duplicate hover/completion
1139
+ verification: true
1140
+ },
1141
+ });
1142
+ }
1143
+
1005
1144
  // Sort mappings by source offset
1006
1145
  mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
1007
1146
 
1147
+ // Add a mapping for the very beginning of the file to handle import additions
1148
+ // This ensures that code actions adding imports at the top work correctly
1149
+ if (mappings.length > 0 && mappings[0].sourceOffsets[0] > 0) {
1150
+ mappings.unshift({
1151
+ sourceOffsets: [0],
1152
+ generatedOffsets: [0],
1153
+ lengths: [1],
1154
+ data: {
1155
+ ...mapping_data,
1156
+ codeActions: true, // auto-import
1157
+ rename: false, // avoid rename for a “dummy” mapping
1158
+ }
1159
+ });
1160
+ }
1161
+
1008
1162
  return {
1009
1163
  code: generated_code,
1010
1164
  mappings,
@@ -1,10 +1,10 @@
1
- /** @import { Block, Derived, Tracked } from '#client' */
1
+ /** @import { Block, Tracked } from '#client' */
2
2
 
3
- import { destroy_block, root } from './internal/client/blocks.js';
4
- import { handle_root_events } from './internal/client/events.js';
3
+ import { destroy_block, effect, render, root } from './internal/client/blocks.js';
4
+ import { handle_root_events, on } from './internal/client/events.js';
5
5
  import { init_operations } from './internal/client/operations.js';
6
- import { active_block, tracked, derived } from './internal/client/runtime.js';
7
- import { create_anchor } from './internal/client/utils.js';
6
+ import { active_block, get, set, tick } from './internal/client/runtime.js';
7
+ import { create_anchor, is_array, is_tracked_object } from './internal/client/utils.js';
8
8
  import { remove_ssr_css } from './internal/client/css.js';
9
9
 
10
10
  // Re-export JSX runtime functions for jsxImportSource: "ripple"
@@ -77,3 +77,183 @@ export { Portal } from './internal/client/portal.js';
77
77
  export { ref_prop as createRefKey } from './internal/client/runtime.js';
78
78
 
79
79
  export { on } from './internal/client/events.js';
80
+
81
+ /**
82
+ * @param {string} value
83
+ */
84
+ function to_number(value) {
85
+ return value === '' ? null : +value;
86
+ }
87
+
88
+ /**
89
+ * @param {HTMLInputElement} input
90
+ */
91
+ function is_numberlike_input(input) {
92
+ var type = input.type;
93
+ return type === 'number' || type === 'range';
94
+ }
95
+
96
+ /** @param {HTMLOptionElement} option */
97
+ function get_option_value(option) {
98
+ return option.value;
99
+ }
100
+
101
+ /**
102
+ * Selects the correct option(s) (depending on whether this is a multiple select)
103
+ * @template V
104
+ * @param {HTMLSelectElement} select
105
+ * @param {V} value
106
+ * @param {boolean} mounting
107
+ */
108
+ function select_option(select, value, mounting = false) {
109
+ if (select.multiple) {
110
+ // If value is null or undefined, keep the selection as is
111
+ if (value == undefined) {
112
+ return;
113
+ }
114
+
115
+ // If not an array, warn and keep the selection as is
116
+ if (!is_array(value)) {
117
+ // TODO
118
+ }
119
+
120
+ // Otherwise, update the selection
121
+ for (var option of select.options) {
122
+ option.selected = /** @type {string[]} */ (value).includes(get_option_value(option));
123
+ }
124
+
125
+ return;
126
+ }
127
+
128
+ for (option of select.options) {
129
+ var option_value = get_option_value(option);
130
+ if (option_value === value) {
131
+ option.selected = true;
132
+ return;
133
+ }
134
+ }
135
+
136
+ if (!mounting || value !== undefined) {
137
+ select.selectedIndex = -1; // no option should be selected
138
+ }
139
+ }
140
+
141
+ /**
142
+ * @param {unknown} maybe_tracked
143
+ * @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
144
+ */
145
+ export function bindValue(maybe_tracked) {
146
+ if (!is_tracked_object(maybe_tracked)) {
147
+ throw new TypeError('bindValue() argument is not a tracked object');
148
+ }
149
+
150
+ var block = /** @type {Block} */ (active_block);
151
+ var tracked = /** @type {Tracked} */ (maybe_tracked);
152
+
153
+ return (node) => {
154
+ var clear_event;
155
+
156
+ if (node.tagName === 'SELECT') {
157
+ var select = /** @type {HTMLSelectElement} */ (node);
158
+ var mounting = true;
159
+
160
+ clear_event = on(select, 'change', async () => {
161
+ var query = ':checked';
162
+ /** @type {unknown} */
163
+ var value;
164
+
165
+ if (select.multiple) {
166
+ value = [].map.call(select.querySelectorAll(query), get_option_value);
167
+ } else {
168
+ /** @type {HTMLOptionElement | null} */
169
+ var selected_option =
170
+ select.querySelector(query) ??
171
+ // will fall back to first non-disabled option if no option is selected
172
+ select.querySelector('option:not([disabled])');
173
+ value = selected_option && get_option_value(selected_option);
174
+ }
175
+
176
+ set(tracked, value, block);
177
+ });
178
+
179
+ effect(() => {
180
+ var value = get(tracked);
181
+ select_option(select, value, mounting);
182
+
183
+ // Mounting and value undefined -> take selection from dom
184
+ if (mounting && value === undefined) {
185
+ /** @type {HTMLOptionElement | null} */
186
+ var selected_option = select.querySelector(':checked');
187
+ if (selected_option !== null) {
188
+ value = get_option_value(selected_option);
189
+ set(tracked, value, block);
190
+ }
191
+ }
192
+
193
+ mounting = false;
194
+ });
195
+ } else {
196
+ var input = /** @type {HTMLInputElement} */ (node);
197
+
198
+ clear_event = on(input, 'input', async () => {
199
+ /** @type {any} */
200
+ var value = input.value;
201
+ value = is_numberlike_input(input) ? to_number(value) : value;
202
+ set(tracked, value, block);
203
+
204
+ await tick();
205
+
206
+ if (value !== (value = get(tracked))) {
207
+ var start = input.selectionStart;
208
+ var end = input.selectionEnd;
209
+ input.value = value ?? '';
210
+
211
+ // Restore selection
212
+ if (end !== null) {
213
+ input.selectionStart = start;
214
+ input.selectionEnd = Math.min(end, input.value.length);
215
+ }
216
+ }
217
+ });
218
+
219
+ render(() => {
220
+ var value = get(tracked);
221
+
222
+ if (is_numberlike_input(input) && value === to_number(input.value)) {
223
+ return;
224
+ }
225
+
226
+ if (input.type === 'date' && !value && !input.value) {
227
+ return;
228
+ }
229
+
230
+ if (value !== input.value) {
231
+ input.value = value ?? '';
232
+ }
233
+ });
234
+
235
+ return clear_event;
236
+ }
237
+ };
238
+ }
239
+
240
+ /**
241
+ * @param {unknown} maybe_tracked
242
+ * @returns {(node: HTMLInputElement) => void}
243
+ */
244
+ export function bindChecked(maybe_tracked) {
245
+ if (!is_tracked_object(maybe_tracked)) {
246
+ throw new TypeError('bindChecked() argument is not a tracked object');
247
+ }
248
+
249
+ const block = /** @type {any} */ (active_block);
250
+ const tracked = /** @type {Tracked<any>} */ (maybe_tracked);
251
+
252
+ return (input) => {
253
+ const clear_event = on(input, 'change', () => {
254
+ set(tracked, input.checked, block);
255
+ });
256
+
257
+ return clear_event;
258
+ };
259
+ }
@@ -269,16 +269,16 @@ var empty_get_set = { get: undefined, set: undefined };
269
269
  /**
270
270
  *
271
271
  * @param {any} v
272
- * @param {Block} b
272
+ * @param {Block} block
273
273
  * @param {(value: any) => any} [get]
274
274
  * @param {(next: any, prev: any) => any} [set]
275
275
  * @returns {Tracked}
276
276
  */
277
- export function tracked(v, b, get, set) {
277
+ export function tracked(v, block, get, set) {
278
278
  // TODO: now we expose tracked, we should likely block access in DEV somehow
279
279
  return {
280
280
  a: get || set ? { get, set } : empty_get_set,
281
- b,
281
+ b: block || active_block,
282
282
  c: 0,
283
283
  f: TRACKED,
284
284
  v,
@@ -295,7 +295,7 @@ export function tracked(v, b, get, set) {
295
295
  export function derived(fn, block, get, set) {
296
296
  return {
297
297
  a: get || set ? { get, set } : empty_get_set,
298
- b: block,
298
+ b: block || active_block,
299
299
  blocks: null,
300
300
  c: 0,
301
301
  co: active_component,