ripple 0.2.105 → 0.2.106

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.105",
6
+ "version": "0.2.106",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -170,13 +170,14 @@ function RipplePlugin(config) {
170
170
  // Look ahead to see if this is followed by a valid identifier character or opening paren
171
171
  if (this.pos + 1 < this.input.length) {
172
172
  const nextChar = this.input.charCodeAt(this.pos + 1);
173
-
173
+
174
174
  // Check if this is @( for unboxing expression syntax
175
- if (nextChar === 40) { // ( character
175
+ if (nextChar === 40) {
176
+ // ( character
176
177
  this.pos += 2; // skip '@('
177
178
  return this.finishToken(tt.parenL, '@(');
178
179
  }
179
-
180
+
180
181
  // Check if the next character can start an identifier
181
182
  if (
182
183
  (nextChar >= 65 && nextChar <= 90) || // A-Z
@@ -359,7 +360,7 @@ function RipplePlugin(config) {
359
360
  if (this.type === tt.parenL && this.value === '@(') {
360
361
  return this.parseTrackedExpression();
361
362
  }
362
-
363
+
363
364
  // Check if this is a tuple literal starting with #[
364
365
  if (this.type === tt.bracketL && this.value === '#[') {
365
366
  return this.parseTrackedArrayExpression();
@@ -1319,6 +1320,19 @@ function RipplePlugin(config) {
1319
1320
  }
1320
1321
  }
1321
1322
 
1323
+ if (this.type.label === 'jsxTagStart') {
1324
+ this.next();
1325
+ if (this.value === '/') {
1326
+ this.unexpected();
1327
+ }
1328
+ const node = this.parseElement();
1329
+
1330
+ if (!node) {
1331
+ this.unexpected();
1332
+ }
1333
+ return node;
1334
+ }
1335
+
1322
1336
  return super.parseStatement(context, topLevel, exports);
1323
1337
  }
1324
1338
 
@@ -34,7 +34,8 @@ function mark_control_flow_has_template(path) {
34
34
  node.type === 'ForInStatement' ||
35
35
  node.type === 'ForOfStatement' ||
36
36
  node.type === 'TryStatement' ||
37
- node.type === 'IfStatement'
37
+ node.type === 'IfStatement' ||
38
+ node.type === 'SwitchStatement'
38
39
  ) {
39
40
  node.metadata.has_template = true;
40
41
  }
@@ -106,7 +107,7 @@ const visitors = {
106
107
  }
107
108
 
108
109
  if (
109
- is_reference(node, /** @type {Node} */(parent)) &&
110
+ is_reference(node, /** @type {Node} */ (parent)) &&
110
111
  node.tracked &&
111
112
  binding?.node !== node
112
113
  ) {
@@ -117,7 +118,7 @@ const visitors = {
117
118
  }
118
119
 
119
120
  if (
120
- is_reference(node, /** @type {Node} */(parent)) &&
121
+ is_reference(node, /** @type {Node} */ (parent)) &&
121
122
  node.tracked &&
122
123
  binding?.node !== node
123
124
  ) {
@@ -253,7 +254,12 @@ const visitors = {
253
254
  // Track metadata for this component
254
255
  const metadata = { await: false };
255
256
 
256
- context.next({ ...context.state, elements, function_depth: context.state.function_depth + 1, metadata });
257
+ context.next({
258
+ ...context.state,
259
+ elements,
260
+ function_depth: context.state.function_depth + 1,
261
+ metadata,
262
+ });
257
263
 
258
264
  const css = node.css;
259
265
 
@@ -282,6 +288,41 @@ const visitors = {
282
288
  context.next();
283
289
  },
284
290
 
291
+ SwitchStatement(node, context) {
292
+ if (!is_inside_component(context)) {
293
+ return context.next();
294
+ }
295
+
296
+ context.visit(node.discriminant, context.state);
297
+
298
+ for (const switch_case of node.cases) {
299
+ // Validate that each cases ends in a break statement, except for the last case
300
+ const last = switch_case.consequent?.[switch_case.consequent.length - 1];
301
+
302
+ if (last.type !== 'BreakStatement' && node.cases.indexOf(switch_case) !== node.cases.length - 1) {
303
+ error(
304
+ 'Template switch cases must end with a break statement (with the exception of the last case).',
305
+ context.state.analysis.module.filename,
306
+ switch_case,
307
+ );
308
+ }
309
+
310
+ node.metadata = {
311
+ has_template: false,
312
+ };
313
+
314
+ context.visit(switch_case, context.state);
315
+
316
+ if (!node.metadata.has_template) {
317
+ error(
318
+ 'Component switch statements must contain a template in each of their cases. Move the switch statement into an effect if it does not render anything.',
319
+ context.state.analysis.module.filename,
320
+ node,
321
+ );
322
+ }
323
+ }
324
+ },
325
+
285
326
  ForOfStatement(node, context) {
286
327
  if (!is_inside_component(context)) {
287
328
  return context.next();
@@ -1,4 +1,4 @@
1
- /** @import {Expression, FunctionExpression, Pattern} from 'estree' */
1
+ /** @import {Expression, FunctionExpression, Pattern, Node, Program} from 'estree' */
2
2
 
3
3
  import { walk } from 'zimmerframe';
4
4
  import path from 'node:path';
@@ -72,7 +72,7 @@ function visit_function(node, context) {
72
72
  let body = context.visit(node.body, {
73
73
  ...state,
74
74
  // we are new context so tracking no longer applies
75
- metadata: { ...state.metadata, tracked: false },
75
+ metadata: { ...state.metadata, tracking: false },
76
76
  });
77
77
 
78
78
  if (metadata?.tracked === true) {
@@ -159,9 +159,11 @@ const visitors = {
159
159
  }
160
160
  } else {
161
161
  const binding = context.state.scope.get(node.name);
162
+ const isRightSideOfAssignment = parent.type === 'AssignmentExpression' && parent.right === node;
162
163
  if (
163
164
  (context.state.metadata?.tracking === false ||
164
- (parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression')) &&
165
+ (parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression') ||
166
+ isRightSideOfAssignment) &&
165
167
  (node.tracked ||
166
168
  binding?.kind === 'prop' ||
167
169
  binding?.kind === 'index' ||
@@ -175,10 +177,10 @@ const visitors = {
175
177
  add_ripple_internal_import(context);
176
178
  return b.call('_$_.get', build_getter(node, context));
177
179
  }
178
- }
179
180
 
180
- add_ripple_internal_import(context);
181
- return build_getter(node, context);
181
+ add_ripple_internal_import(context);
182
+ return build_getter(node, context);
183
+ }
182
184
  }
183
185
  }
184
186
  },
@@ -467,10 +469,9 @@ const visitors = {
467
469
 
468
470
  const handle_static_attr = (name, value) => {
469
471
  const attr_value = b.literal(
470
- ` ${name}${
471
- is_boolean_attribute(name) && value === true
472
- ? ''
473
- : `="${value === true ? '' : escape_html(value, true)}"`
472
+ ` ${name}${is_boolean_attribute(name) && value === true
473
+ ? ''
474
+ : `="${value === true ? '' : escape_html(value, true)}"`
474
475
  }`,
475
476
  );
476
477
 
@@ -942,10 +943,10 @@ const visitors = {
942
943
  operator === '='
943
944
  ? context.visit(right)
944
945
  : b.binary(
945
- operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
946
- /** @type {Pattern} */ (context.visit(left)),
947
- /** @type {Expression} */ (context.visit(right)),
948
- ),
946
+ operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
947
+ /** @type {Pattern} */(context.visit(left)),
948
+ /** @type {Expression} */(context.visit(right)),
949
+ ),
949
950
  b.id('__block'),
950
951
  );
951
952
  }
@@ -961,12 +962,12 @@ const visitors = {
961
962
  operator === '='
962
963
  ? context.visit(right)
963
964
  : b.binary(
964
- operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
965
- /** @type {Pattern} */ (
966
- context.visit(left, { ...context.state, metadata: { tracking: false } })
967
- ),
968
- /** @type {Expression} */ (context.visit(right)),
965
+ operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
966
+ /** @type {Pattern} */(
967
+ context.visit(left, { ...context.state, metadata: { tracking: false } })
969
968
  ),
969
+ /** @type {Expression} */(context.visit(right)),
970
+ ),
970
971
  b.id('__block'),
971
972
  );
972
973
  }
@@ -1014,7 +1015,6 @@ const visitors = {
1014
1015
  );
1015
1016
  }
1016
1017
 
1017
-
1018
1018
  const left = object(argument);
1019
1019
  const binding = context.state.scope.get(left.name);
1020
1020
  const transformers = left && binding?.transform;
@@ -1075,6 +1075,52 @@ const visitors = {
1075
1075
  );
1076
1076
  },
1077
1077
 
1078
+ SwitchStatement(node, context) {
1079
+ if (!is_inside_component(context)) {
1080
+ return context.next();
1081
+ }
1082
+ context.state.template.push('<!>');
1083
+
1084
+ const id = context.state.flush_node();
1085
+ const statements = [];
1086
+ const cases = [];
1087
+
1088
+ let i = 1;
1089
+
1090
+ for (const switch_case of node.cases) {
1091
+ const consequent_scope =
1092
+ context.state.scopes.get(switch_case.consequent) || context.state.scope;
1093
+ const consequent_id = context.state.scope.generate('switch_case_' + (switch_case.test == null ? 'default' : i));
1094
+ const consequent = b.block(
1095
+ transform_body(switch_case.consequent, {
1096
+ ...context,
1097
+ state: { ...context.state, scope: consequent_scope },
1098
+ }),
1099
+ );
1100
+
1101
+ statements.push(b.var(b.id(consequent_id), b.arrow([b.id('__anchor')], consequent)));
1102
+
1103
+ cases.push(
1104
+ b.switch_case(switch_case.test ? context.visit(switch_case.test) : null, [
1105
+ b.return(b.id(consequent_id)),
1106
+ ]),
1107
+ );
1108
+ i++;
1109
+ }
1110
+
1111
+ statements.push(
1112
+ b.stmt(
1113
+ b.call(
1114
+ '_$_.switch',
1115
+ id,
1116
+ b.thunk(b.block([b.switch(context.visit(node.discriminant), cases)])),
1117
+ ),
1118
+ ),
1119
+ );
1120
+
1121
+ context.state.init.push(b.block(statements));
1122
+ },
1123
+
1078
1124
  IfStatement(node, context) {
1079
1125
  if (!is_inside_component(context)) {
1080
1126
  return context.next();
@@ -1126,12 +1172,12 @@ const visitors = {
1126
1172
  b.stmt(b.call(b.id('__render'), b.id(consequent_id))),
1127
1173
  alternate_id
1128
1174
  ? b.stmt(
1129
- b.call(
1130
- b.id('__render'),
1131
- b.id(alternate_id),
1132
- node.alternate ? b.literal(false) : undefined,
1133
- ),
1134
- )
1175
+ b.call(
1176
+ b.id('__render'),
1177
+ b.id(alternate_id),
1178
+ node.alternate ? b.literal(false) : undefined,
1179
+ ),
1180
+ )
1135
1181
  : undefined,
1136
1182
  ),
1137
1183
  ]),
@@ -1184,9 +1230,9 @@ const visitors = {
1184
1230
  node.handler === null
1185
1231
  ? b.literal(null)
1186
1232
  : b.arrow(
1187
- [b.id('__anchor'), ...(node.handler.param ? [node.handler.param] : [])],
1188
- b.block(transform_body(node.handler.body.body, context)),
1189
- ),
1233
+ [b.id('__anchor'), ...(node.handler.param ? [node.handler.param] : [])],
1234
+ b.block(transform_body(node.handler.body.body, context)),
1235
+ ),
1190
1236
  node.pending === null
1191
1237
  ? undefined
1192
1238
  : b.arrow([b.id('__anchor')], b.block(transform_body(node.pending.body, context))),
@@ -1282,7 +1328,7 @@ function join_template(items) {
1282
1328
  }
1283
1329
 
1284
1330
  for (const quasi of template.quasis) {
1285
- quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
1331
+ quasi.value.raw = sanitize_template_string(/** @type {string} */(quasi.value.cooked));
1286
1332
  }
1287
1333
 
1288
1334
  quasi.tail = true;
@@ -1483,6 +1529,7 @@ function transform_children(children, context) {
1483
1529
  node.type === 'IfStatement' ||
1484
1530
  node.type === 'TryStatement' ||
1485
1531
  node.type === 'ForOfStatement' ||
1532
+ node.type === 'SwitchStatement' ||
1486
1533
  node.type === 'Html' ||
1487
1534
  (node.type === 'Element' &&
1488
1535
  (node.id.type !== 'Identifier' || !is_element_dom_element(node))),
@@ -1634,6 +1681,9 @@ function transform_children(children, context) {
1634
1681
  } else if (node.type === 'TryStatement') {
1635
1682
  node.is_controlled = is_controlled;
1636
1683
  visit(node, { ...state, flush_node, namespace: state.namespace });
1684
+ } else if (node.type === 'SwitchStatement') {
1685
+ node.is_controlled = is_controlled;
1686
+ visit(node, { ...state, flush_node, namespace: state.namespace });
1637
1687
  } else {
1638
1688
  debugger;
1639
1689
  }
@@ -1715,7 +1765,7 @@ export function transform_client(filename, source, analysis, to_ts) {
1715
1765
  to_ts,
1716
1766
  };
1717
1767
 
1718
- const program = /** @type {ESTree.Program} */ (
1768
+ const program = /** @type {Program} */ (
1719
1769
  walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
1720
1770
  );
1721
1771
 
@@ -808,7 +808,7 @@ export function normalize_children(children, context) {
808
808
  * @param {TransformContext} context
809
809
  */
810
810
  function normalize_child(node, normalized, context) {
811
- if (node.type === 'EmptyStatement') {
811
+ if (node.type === 'EmptyStatement' || node.type === 'BreakStatement') {
812
812
  return;
813
813
  } else if (
814
814
  node.type === 'Element' &&
@@ -0,0 +1,34 @@
1
+ /** @import { createSubscriber } from '#public' */
2
+ import { untrack, queue_microtask } from './internal/client/runtime.js';
3
+ import { effect } from './internal/client/blocks.js'
4
+
5
+ /** @type {createSubscriber} */
6
+ export function createSubscriber(start) {
7
+ let subscribers = 0;
8
+ /** @type {(() => void) | void} */
9
+ let stop;
10
+
11
+ return () => {
12
+ effect(() => {
13
+ if (subscribers === 0) {
14
+ stop = untrack(start);
15
+ }
16
+
17
+ subscribers += 1;
18
+
19
+ return () => {
20
+ queue_microtask(() => {
21
+ // Only count down after a microtask, else we would reach 0 before our own render effect reruns,
22
+ // but reach 1 again when the tick callback of the prior teardown runs. That would mean we
23
+ // re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
24
+ subscribers -= 1;
25
+
26
+ if (subscribers === 0) {
27
+ stop?.();
28
+ stop = undefined;
29
+ }
30
+ });
31
+ };
32
+ });
33
+ };
34
+ }
@@ -62,6 +62,14 @@ export { TrackedMap } from './map.js';
62
62
 
63
63
  export { TrackedDate } from './date.js';
64
64
 
65
+ export { TrackedURL } from './url.js';
66
+
67
+ export { TrackedURLSearchParams } from './url-search-params.js';
68
+
69
+ export { createSubscriber } from './create-subscriber.js';
70
+
71
+ export { MediaQuery } from './media-query.js';
72
+
65
73
  export { user_effect as effect } from './internal/client/blocks.js';
66
74
 
67
75
  export { Portal } from './internal/client/portal.js';
@@ -10,8 +10,6 @@ export function effect() {
10
10
  // NO-OP
11
11
  }
12
12
 
13
- export const TrackedArray = Array;
14
-
15
13
  var empty_get_set = { get: undefined, set: undefined };
16
14
 
17
15
  /**
@@ -44,3 +42,30 @@ export function track(v, get, set) {
44
42
  v,
45
43
  };
46
44
  }
45
+
46
+ export const TrackedObject = globalThis.Object;
47
+ export const TrackedArray = globalThis.Array;
48
+ export const TrackedDate = globalThis.Date;
49
+ export const TrackedSet = globalThis.Set;
50
+ export const TrackedMap = globalThis.Map;
51
+ export const TrackedURL = globalThis.URL;
52
+ export const TrackedURLSearchParams = globalThis.URLSearchParams;
53
+
54
+ /**
55
+ * @param {string} query A media query string
56
+ * @param {boolean} [matches] Fallback value for the server
57
+ */
58
+ export function MediaQuery(query, matches = false) {
59
+ if (!new.target) {
60
+ throw new TypeError('MediaQuery must be called with new');
61
+ }
62
+
63
+ return matches;
64
+ }
65
+
66
+ /**
67
+ * @param {any} _
68
+ */
69
+ export function createSubscriber(_) {
70
+ return () => { /* NO-OP */ };
71
+ }
@@ -5,19 +5,20 @@ export var BRANCH_BLOCK = 1 << 4;
5
5
  export var FOR_BLOCK = 1 << 5;
6
6
  export var TRY_BLOCK = 1 << 6;
7
7
  export var IF_BLOCK = 1 << 7;
8
- export var COMPOSITE_BLOCK = 1 << 8;
9
- export var ASYNC_BLOCK = 1 << 9;
10
- export var HEAD_BLOCK = 1 << 10;
11
- export var CONTAINS_UPDATE = 1 << 11;
12
- export var CONTAINS_TEARDOWN = 1 << 12;
13
- export var BLOCK_HAS_RUN = 1 << 13;
14
- export var TRACKED = 1 << 14;
15
- export var DERIVED = 1 << 15;
16
- export var DEFERRED = 1 << 16;
17
- export var PAUSED = 1 << 17;
18
- export var DESTROYED = 1 << 18;
8
+ export var SWITCH_BLOCK = 1 << 8;
9
+ export var COMPOSITE_BLOCK = 1 << 9;
10
+ export var ASYNC_BLOCK = 1 << 10;
11
+ export var HEAD_BLOCK = 1 << 11;
12
+ export var CONTAINS_UPDATE = 1 << 12;
13
+ export var CONTAINS_TEARDOWN = 1 << 13;
14
+ export var BLOCK_HAS_RUN = 1 << 14;
15
+ export var TRACKED = 1 << 15;
16
+ export var DERIVED = 1 << 16;
17
+ export var DEFERRED = 1 << 17;
18
+ export var PAUSED = 1 << 18;
19
+ export var DESTROYED = 1 << 19;
19
20
 
20
- export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
21
+ export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | SWITCH_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
21
22
 
22
23
  export var UNINITIALIZED = Symbol();
23
24
  /** @type {unique symbol} */
@@ -57,6 +57,8 @@ export { if_block as if } from './if.js';
57
57
 
58
58
  export { try_block as try, aborted } from './try.js';
59
59
 
60
+ export { switch_block as switch } from './switch.js';
61
+
60
62
  export { template, append } from './template.js';
61
63
 
62
64
  export { tracked_array } from '../../array.js';
@@ -172,7 +172,7 @@ function run_derived(computed) {
172
172
  var previous_tracking = tracking;
173
173
  var previous_dependency = active_dependency;
174
174
  var previous_component = active_component;
175
- var previous_is_mutating_allowed = is_mutating_allowed;
175
+ var previous_is_mutating_allowed = is_mutating_allowed;
176
176
 
177
177
  try {
178
178
  active_block = computed.b;
@@ -180,7 +180,7 @@ function run_derived(computed) {
180
180
  tracking = true;
181
181
  active_dependency = null;
182
182
  active_component = computed.co;
183
- is_mutating_allowed = false;
183
+ is_mutating_allowed = false;
184
184
 
185
185
  destroy_computed_children(computed);
186
186
 
@@ -195,7 +195,7 @@ function run_derived(computed) {
195
195
  tracking = previous_tracking;
196
196
  active_dependency = previous_dependency;
197
197
  active_component = previous_component;
198
- is_mutating_allowed = previous_is_mutating_allowed;
198
+ is_mutating_allowed = previous_is_mutating_allowed;
199
199
  }
200
200
  }
201
201
 
@@ -270,8 +270,8 @@ var empty_get_set = { get: undefined, set: undefined };
270
270
  *
271
271
  * @param {any} v
272
272
  * @param {Block} b
273
- * @param {Function} [get]
274
- * @param {Function} [set]
273
+ * @param {(value: any) => any} [get]
274
+ * @param {(next: any, prev: any) => any} [set]
275
275
  * @returns {Tracked}
276
276
  */
277
277
  export function tracked(v, b, get, set) {
@@ -288,8 +288,8 @@ export function tracked(v, b, get, set) {
288
288
  /**
289
289
  * @param {any} fn
290
290
  * @param {any} block
291
- * @param {Function} [get]
292
- * @param {Function} [set]
291
+ * @param {(value: any) => any} [get]
292
+ * @param {(next: any, prev: any) => any} [set]
293
293
  * @returns {Derived}
294
294
  */
295
295
  export function derived(fn, block, get, set) {
@@ -510,17 +510,17 @@ export function async_computed(fn, block) {
510
510
 
511
511
  /**
512
512
  * @template V
513
- * @param {Function} fn
514
- * @param {V} v
513
+ * @param {Function} fn
514
+ * @param {V} v
515
515
  */
516
516
  function trigger_track_get(fn, v) {
517
- var previous_is_mutating_allowed = is_mutating_allowed;
518
- try {
519
- is_mutating_allowed = false;
520
- return untrack(() => fn(v));
521
- } finally {
522
- is_mutating_allowed = previous_is_mutating_allowed;
523
- }
517
+ var previous_is_mutating_allowed = is_mutating_allowed;
518
+ try {
519
+ is_mutating_allowed = false;
520
+ return untrack(() => fn(v));
521
+ } finally {
522
+ is_mutating_allowed = previous_is_mutating_allowed;
523
+ }
524
524
  }
525
525
 
526
526
  /**
@@ -774,9 +774,9 @@ export function get_tracked(tracked) {
774
774
  * @param {Block} block
775
775
  */
776
776
  export function set(tracked, value, block) {
777
- if (!is_mutating_allowed) {
778
- throw new Error('Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation');
779
- }
777
+ if (!is_mutating_allowed) {
778
+ throw new Error('Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation');
779
+ }
780
780
 
781
781
  var old_value = tracked.v;
782
782
 
@@ -0,0 +1,32 @@
1
+ /** @import { Block } from '#client' */
2
+
3
+ import { branch, destroy_block, render } from './blocks.js';
4
+ import { SWITCH_BLOCK } from './constants.js';
5
+
6
+ /**
7
+ * @param {Node} node
8
+ * @param {() => ((anchor: Node) => void)} fn
9
+ * @returns {void}
10
+ */
11
+ export function switch_block(node, fn) {
12
+ var anchor = node;
13
+ /** @type {any} */
14
+ var current_branch = null;
15
+ /** @type {Block | null} */
16
+ var b = null;
17
+
18
+ render(() => {
19
+ const branch_fn = fn() ?? null;
20
+ if (current_branch === branch_fn) return;
21
+ current_branch = branch_fn;
22
+
23
+ if (b !== null) {
24
+ destroy_block(b);
25
+ b = null;
26
+ }
27
+
28
+ if (branch_fn !== null) {
29
+ b = branch(() => branch_fn(anchor));
30
+ }
31
+ }, SWITCH_BLOCK);
32
+ }
@@ -0,0 +1,39 @@
1
+ import { on } from './internal/client/events.js';
2
+ import { get, safe_scope, set, tracked } from './internal/client/index.js';
3
+ import { ReactiveValue } from './reactive-value.js';
4
+
5
+ const parenthesis_regex = /\(.+\)/;
6
+ const non_parenthesized_keywords = new Set(['all', 'print', 'screen', 'and', 'or', 'not', 'only']);
7
+
8
+ /**
9
+ * @constructor
10
+ * @param {string} query
11
+ * @param {boolean | undefined} [fallback]
12
+ * @returns {ReactiveValue<boolean>}
13
+ */
14
+ export function MediaQuery(query, fallback) {
15
+ if (!new.target) {
16
+ throw new TypeError('MediaQuery must be called with new');
17
+ }
18
+
19
+ var block = safe_scope();
20
+
21
+ let final_query =
22
+ parenthesis_regex.test(query) ||
23
+ // we need to use `some` here because technically this `window.matchMedia('random,screen')` still returns true
24
+ query.split(/[\s,]+/).some((keyword) => non_parenthesized_keywords.has(keyword.trim()))
25
+ ? query
26
+ : `(${query})`;
27
+ const q = window.matchMedia(final_query);
28
+ const matches = tracked(q.matches, block);
29
+
30
+ return new ReactiveValue(
31
+ () => get(matches),
32
+ () => on(q, 'change', () => {
33
+ // skip wrapping in untrack as createSubscriber already does it
34
+ if (q.matches !== get(matches)) {
35
+ set(matches, q.matches, block)
36
+ }
37
+ })
38
+ );
39
+ }
@@ -0,0 +1,21 @@
1
+ /** @import { Derived } from '#client' */
2
+ import { createSubscriber } from './create-subscriber.js';
3
+ import { safe_scope, derived } from './internal/client/runtime.js';
4
+
5
+ /**
6
+ * @template V
7
+ * @constructor
8
+ * @param {() => V} fn
9
+ * @param {() => void | (() => void)} start
10
+ * @returns {Derived}
11
+ */
12
+ export function ReactiveValue(fn, start) {
13
+ if (!new.target) {
14
+ throw new TypeError('`ReactiveValue` must be called with new');
15
+ }
16
+
17
+ const s = createSubscriber(start);
18
+ const block = safe_scope();
19
+
20
+ return (derived(fn, block, () => { s(); return fn(); }, (_, prev) => prev));
21
+ }