ripple 0.2.168 → 0.2.170
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 +2 -2
- package/src/compiler/phases/2-analyze/index.js +4 -5
- package/src/compiler/phases/2-analyze/prune.js +23 -8
- package/src/compiler/phases/3-transform/client/index.js +20 -36
- package/src/compiler/phases/3-transform/stylesheet.js +1 -1
- package/src/compiler/utils.js +13 -6
- package/src/jsx-runtime.d.ts +5 -3
- package/src/runtime/internal/client/events.js +134 -25
- package/src/runtime/internal/client/index.js +1 -1
- package/src/runtime/internal/client/render.js +36 -26
- package/src/runtime/media-query.js +14 -8
- package/src/utils/events.js +120 -43
- package/tests/client/basic/basic.attributes.test.ripple +442 -0
- package/tests/client/basic/basic.events.test.ripple +2 -2
- package/tests/client/composite/composite.reactivity.test.ripple +6 -5
- package/tests/client/css/global-keyframes.test.ripple +57 -0
- package/tests/client/input-value.test.ripple +4 -4
- package/tests/client/media-query.test.ripple +1 -1
- package/tests/utils/events.test.js +57 -46
- package/types/index.d.ts +15 -5
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.
|
|
6
|
+
"version": "0.2.170",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"ripple": "0.2.
|
|
84
|
+
"ripple": "0.2.170"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -2,7 +2,7 @@ import * as b from '../../../utils/builders.js';
|
|
|
2
2
|
import { walk } from 'zimmerframe';
|
|
3
3
|
import { create_scopes, ScopeRoot } from '../../scope.js';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
is_delegated_event,
|
|
6
6
|
get_parent_block_node,
|
|
7
7
|
is_element_dom_element,
|
|
8
8
|
is_inside_component,
|
|
@@ -729,16 +729,15 @@ const visitors = {
|
|
|
729
729
|
}
|
|
730
730
|
|
|
731
731
|
if (is_event_attribute(attr.name.name)) {
|
|
732
|
-
const event_name = attr.name.name.slice(2).toLowerCase();
|
|
733
732
|
const handler = visit(attr.value, state);
|
|
734
|
-
const
|
|
733
|
+
const is_delegated = is_delegated_event(attr.name.name, handler, context);
|
|
735
734
|
|
|
736
|
-
if (
|
|
735
|
+
if (is_delegated) {
|
|
737
736
|
if (attr.metadata === undefined) {
|
|
738
737
|
attr.metadata = {};
|
|
739
738
|
}
|
|
740
739
|
|
|
741
|
-
attr.metadata.delegated =
|
|
740
|
+
attr.metadata.delegated = is_delegated;
|
|
742
741
|
}
|
|
743
742
|
} else if (attr.value !== null) {
|
|
744
743
|
visit(attr.value, state);
|
|
@@ -845,6 +845,26 @@ function get_parent_rules(rule) {
|
|
|
845
845
|
return rules;
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Check if a CSS rule contains animation or animation-name properties
|
|
850
|
+
* @param {Compiler.AST.CSS.Rule} rule
|
|
851
|
+
* @returns {boolean}
|
|
852
|
+
*/
|
|
853
|
+
function rule_has_animation(rule) {
|
|
854
|
+
if (!rule.block) return false;
|
|
855
|
+
|
|
856
|
+
for (const child of rule.block.children) {
|
|
857
|
+
if (child.type === 'Declaration') {
|
|
858
|
+
const prop = child.property?.toLowerCase();
|
|
859
|
+
if (prop === 'animation' || prop === 'animation-name') {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
|
|
848
868
|
export function prune_css(css, element) {
|
|
849
869
|
walk(css, null, {
|
|
850
870
|
Rule(node, context) {
|
|
@@ -859,14 +879,9 @@ export function prune_css(css, element) {
|
|
|
859
879
|
|
|
860
880
|
seen.clear();
|
|
861
881
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
/** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule),
|
|
866
|
-
element,
|
|
867
|
-
BACKWARD,
|
|
868
|
-
)
|
|
869
|
-
) {
|
|
882
|
+
const rule = /** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule);
|
|
883
|
+
|
|
884
|
+
if (apply_selector(selectors, rule, element, BACKWARD) || rule_has_animation(rule)) {
|
|
870
885
|
node.metadata.used = true;
|
|
871
886
|
}
|
|
872
887
|
|
|
@@ -40,7 +40,11 @@ import {
|
|
|
40
40
|
import is_reference from 'is-reference';
|
|
41
41
|
import { object } from '../../../../utils/ast.js';
|
|
42
42
|
import { render_stylesheets } from '../stylesheet.js';
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
get_original_event_name,
|
|
45
|
+
is_event_attribute,
|
|
46
|
+
normalize_event_name,
|
|
47
|
+
} from '../../../../utils/events.js';
|
|
44
48
|
import { createHash } from 'node:crypto';
|
|
45
49
|
|
|
46
50
|
function add_ripple_internal_import(context) {
|
|
@@ -976,55 +980,35 @@ const visitors = {
|
|
|
976
980
|
}
|
|
977
981
|
|
|
978
982
|
if (is_event_attribute(name)) {
|
|
979
|
-
|
|
980
|
-
let
|
|
981
|
-
|
|
982
|
-
: name.slice(2).toLowerCase();
|
|
983
|
-
let handler = visit(attr.value, state);
|
|
983
|
+
const metadata = { tracking: false, await: false };
|
|
984
|
+
let handler = visit(attr.value, { ...state, metadata });
|
|
985
|
+
const id = state.flush_node();
|
|
984
986
|
|
|
985
987
|
if (attr.metadata?.delegated) {
|
|
986
|
-
|
|
988
|
+
const event_name = normalize_event_name(name);
|
|
987
989
|
|
|
988
990
|
if (!state.events.has(event_name)) {
|
|
989
991
|
state.events.add(event_name);
|
|
990
992
|
}
|
|
991
993
|
|
|
992
|
-
if (
|
|
993
|
-
(handler.type === 'Identifier' &&
|
|
994
|
-
is_declared_function_within_component(handler, context)) ||
|
|
995
|
-
handler.type === 'ArrowFunctionExpression' ||
|
|
996
|
-
handler.type === 'FunctionExpression'
|
|
997
|
-
) {
|
|
998
|
-
delegated_assignment = handler;
|
|
999
|
-
} else {
|
|
1000
|
-
delegated_assignment = b.array([handler, b.id('__block')]);
|
|
1001
|
-
}
|
|
1002
|
-
const id = state.flush_node();
|
|
1003
|
-
|
|
1004
994
|
state.init.push(
|
|
1005
|
-
b.stmt(b.assignment('=', b.member(id, '__' + event_name),
|
|
995
|
+
b.stmt(b.assignment('=', b.member(id, '__' + event_name), handler)),
|
|
1006
996
|
);
|
|
1007
997
|
} else {
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
b.call(
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
capture && b.true,
|
|
1019
|
-
passive === undefined ? undefined : b.literal(passive),
|
|
1020
|
-
),
|
|
1021
|
-
),
|
|
1022
|
-
);
|
|
998
|
+
const event_name = get_original_event_name(name);
|
|
999
|
+
// Check if handler is reactive (contains tracking)
|
|
1000
|
+
if (metadata.tracking) {
|
|
1001
|
+
// Use reactive_event with a thunk to re-evaluate when dependencies change
|
|
1002
|
+
state.init.push(
|
|
1003
|
+
b.stmt(b.call('_$_.render_event', b.literal(event_name), id, b.thunk(handler))),
|
|
1004
|
+
);
|
|
1005
|
+
} else {
|
|
1006
|
+
state.init.push(b.stmt(b.call('_$_.event', b.literal(event_name), id, handler)));
|
|
1007
|
+
}
|
|
1023
1008
|
}
|
|
1024
1009
|
|
|
1025
1010
|
continue;
|
|
1026
1011
|
}
|
|
1027
|
-
|
|
1028
1012
|
const metadata = { tracking: false, await: false };
|
|
1029
1013
|
const expression = visit(attr.value, { ...state, metadata });
|
|
1030
1014
|
// All other attributes
|
|
@@ -195,7 +195,7 @@ const visitors = {
|
|
|
195
195
|
const character = state.code.original[index];
|
|
196
196
|
|
|
197
197
|
if (regex_css_name_boundary.test(character)) {
|
|
198
|
-
if (
|
|
198
|
+
if (name) {
|
|
199
199
|
const append_index = index - name.length;
|
|
200
200
|
state.keyframes[name] ??= { indexes: [], local: undefined };
|
|
201
201
|
if (state.keyframes[name].local) {
|
package/src/compiler/utils.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** @import { Identifier, Pattern, Super,
|
|
2
|
-
/** @import { Component, Element,
|
|
1
|
+
/** @import { Identifier, Pattern, Super, MemberExpression, AssignmentExpression, Expression, Node, AssignmentOperator } from 'estree' */
|
|
2
|
+
/** @import { Component, Element, RippleNode, TransformContext } from '#compiler' */
|
|
3
3
|
import { build_assignment_value, extract_paths } from '../utils/ast.js';
|
|
4
4
|
import * as b from '../utils/builders.js';
|
|
5
|
-
import {
|
|
5
|
+
import { is_capture_event, is_non_delegated, normalize_event_name } from '../utils/events.js';
|
|
6
6
|
|
|
7
7
|
const regex_return_characters = /\r/g;
|
|
8
8
|
|
|
@@ -173,12 +173,19 @@ export function is_dom_property(name) {
|
|
|
173
173
|
* Determines if an event handler can be delegated
|
|
174
174
|
* @param {string} event_name
|
|
175
175
|
* @param {Expression} handler
|
|
176
|
-
* @param {
|
|
176
|
+
* @param {TransformContext} context
|
|
177
177
|
* @returns {boolean}
|
|
178
178
|
*/
|
|
179
|
-
export function
|
|
179
|
+
export function is_delegated_event(event_name, handler, context) {
|
|
180
180
|
// Handle delegated event handlers. Bail out if not a delegated event.
|
|
181
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
!handler ||
|
|
183
|
+
is_capture_event(event_name) ||
|
|
184
|
+
is_non_delegated(normalize_event_name(event_name)) ||
|
|
185
|
+
(handler.type !== 'FunctionExpression' &&
|
|
186
|
+
handler.type !== 'ArrowFunctionExpression' &&
|
|
187
|
+
!is_declared_function_within_component(/** @type {Identifier}*/ (handler), context))
|
|
188
|
+
) {
|
|
182
189
|
return false;
|
|
183
190
|
}
|
|
184
191
|
return true;
|
package/src/jsx-runtime.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AddEventObject } from '#public';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Ripple JSX Runtime Type Definitions
|
|
3
5
|
* Ripple components are imperative and don't return JSX elements
|
|
@@ -40,9 +42,9 @@ interface HTMLAttributes {
|
|
|
40
42
|
className?: string;
|
|
41
43
|
id?: string;
|
|
42
44
|
style?: string | Record<string, string | number>;
|
|
43
|
-
onClick?:
|
|
44
|
-
onInput?:
|
|
45
|
-
onChange?:
|
|
45
|
+
onClick?: EventListener | AddEventObject;
|
|
46
|
+
onInput?: EventListener | AddEventObject;
|
|
47
|
+
onChange?: EventListener | AddEventObject;
|
|
46
48
|
children?: any;
|
|
47
49
|
[key: string]: any;
|
|
48
50
|
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
/** @import { AddEventObject, AddEventOptions, ExtendedEventOptions } from '#public'*/
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {EventTarget & Record<string, any>} DelegatedEventTarget
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
event_name_from_capture,
|
|
7
|
+
is_capture_event,
|
|
8
|
+
is_non_delegated,
|
|
9
|
+
is_passive_event,
|
|
10
|
+
} from '../../../utils/events.js';
|
|
2
11
|
import {
|
|
3
12
|
active_block,
|
|
4
13
|
active_reaction,
|
|
@@ -8,6 +17,7 @@ import {
|
|
|
8
17
|
tracking,
|
|
9
18
|
} from './runtime.js';
|
|
10
19
|
import { array_from, define_property, is_array } from './utils.js';
|
|
20
|
+
import { render } from './blocks.js';
|
|
11
21
|
|
|
12
22
|
/** @type {Set<string>} */
|
|
13
23
|
var all_registered_events = new Set();
|
|
@@ -15,21 +25,44 @@ var all_registered_events = new Set();
|
|
|
15
25
|
/** @type {Set<(events: Array<string>) => void>} */
|
|
16
26
|
var root_event_handles = new Set();
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @param {AddEventOptions} options
|
|
30
|
+
* @returns {AddEventListenerOptions}
|
|
31
|
+
*/
|
|
32
|
+
function get_event_options(options) {
|
|
33
|
+
/** @type AddEventListenerOptions */
|
|
34
|
+
var event_options = {};
|
|
35
|
+
|
|
36
|
+
if (options.capture) {
|
|
37
|
+
event_options.capture = true;
|
|
38
|
+
}
|
|
39
|
+
if (options.once) {
|
|
40
|
+
event_options.once = true;
|
|
41
|
+
}
|
|
42
|
+
if (options.passive) {
|
|
43
|
+
event_options.passive = true;
|
|
44
|
+
}
|
|
45
|
+
if (options.signal) {
|
|
46
|
+
event_options.signal = options.signal;
|
|
47
|
+
}
|
|
48
|
+
return event_options;
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* @param {EventTarget} element
|
|
20
53
|
* @param {string} type
|
|
21
54
|
* @param {EventListener} handler
|
|
22
|
-
* @param {
|
|
55
|
+
* @param {ExtendedEventOptions} [options]
|
|
23
56
|
*/
|
|
24
57
|
export function on(element, type, handler, options = {}) {
|
|
25
|
-
var
|
|
58
|
+
var remove_listener = create_event(type, element, handler, options);
|
|
26
59
|
|
|
27
60
|
return () => {
|
|
28
|
-
|
|
61
|
+
remove_listener();
|
|
29
62
|
};
|
|
30
63
|
}
|
|
31
64
|
|
|
32
|
-
|
|
65
|
+
var last_propagated_event = null;
|
|
33
66
|
|
|
34
67
|
/**
|
|
35
68
|
* @this {EventTarget}
|
|
@@ -130,12 +163,7 @@ export function handle_event_propagation(event) {
|
|
|
130
163
|
var delegated = current_target['__' + event_name];
|
|
131
164
|
|
|
132
165
|
if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
|
|
133
|
-
|
|
134
|
-
var [fn, block, ...data] = delegated;
|
|
135
|
-
fn.apply(current_target, [event, ...data, block]);
|
|
136
|
-
} else {
|
|
137
|
-
delegated.call(current_target, event);
|
|
138
|
-
}
|
|
166
|
+
delegated.call(current_target, event);
|
|
139
167
|
}
|
|
140
168
|
} catch (error) {
|
|
141
169
|
if (throw_error) {
|
|
@@ -174,12 +202,51 @@ export function handle_event_propagation(event) {
|
|
|
174
202
|
/**
|
|
175
203
|
* @param {string} event_name
|
|
176
204
|
* @param {EventTarget} dom
|
|
177
|
-
* @param {EventListener}
|
|
178
|
-
* @param {
|
|
205
|
+
* @param {EventListener} handler
|
|
206
|
+
* @param {AddEventOptions} options
|
|
207
|
+
* @returns {() => void}
|
|
179
208
|
*/
|
|
180
|
-
function create_event(event_name, dom, handler, options
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
function create_event(event_name, dom, handler, options) {
|
|
210
|
+
var is_delegated = true;
|
|
211
|
+
|
|
212
|
+
if (is_capture_event(event_name)) {
|
|
213
|
+
event_name = event_name_from_capture(event_name);
|
|
214
|
+
|
|
215
|
+
if (!('capture' in options) || options.capture !== false) {
|
|
216
|
+
options.capture = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
event_name =
|
|
221
|
+
options.customName && options.customName?.length
|
|
222
|
+
? options.customName
|
|
223
|
+
: event_name.toLowerCase();
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
options.delegated === false ||
|
|
227
|
+
options.capture ||
|
|
228
|
+
options.passive ||
|
|
229
|
+
options.once ||
|
|
230
|
+
options.signal ||
|
|
231
|
+
is_non_delegated(event_name)
|
|
232
|
+
) {
|
|
233
|
+
is_delegated = false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (is_delegated) {
|
|
237
|
+
var prop = '__' + event_name;
|
|
238
|
+
/** @type {DelegatedEventTarget} */ (dom)[prop] = handler;
|
|
239
|
+
delegate([event_name]);
|
|
240
|
+
return () => {
|
|
241
|
+
/** @type {DelegatedEventTarget} */ (dom)[prop] = undefined;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @type {EventListener}
|
|
247
|
+
* @this {Element}
|
|
248
|
+
*/
|
|
249
|
+
function target_handler(event) {
|
|
183
250
|
var previous_block = active_block;
|
|
184
251
|
var previous_reaction = active_reaction;
|
|
185
252
|
var previous_tracking = tracking;
|
|
@@ -203,22 +270,64 @@ function create_event(event_name, dom, handler, options = {}) {
|
|
|
203
270
|
}
|
|
204
271
|
}
|
|
205
272
|
|
|
206
|
-
|
|
273
|
+
var event_options = get_event_options(options);
|
|
207
274
|
|
|
208
|
-
|
|
275
|
+
dom.addEventListener(event_name, target_handler, event_options);
|
|
276
|
+
return () => {
|
|
277
|
+
dom.removeEventListener(event_name, target_handler, event_options);
|
|
278
|
+
};
|
|
209
279
|
}
|
|
210
280
|
|
|
211
281
|
/**
|
|
212
282
|
* @param {string} event_name
|
|
213
283
|
* @param {Element} dom
|
|
214
|
-
* @param {EventListener}
|
|
215
|
-
* @
|
|
216
|
-
|
|
217
|
-
|
|
284
|
+
* @param {EventListener | AddEventObject} handler
|
|
285
|
+
* @returns {() => void}
|
|
286
|
+
*/
|
|
287
|
+
export function event(event_name, dom, handler) {
|
|
288
|
+
/** @type AddEventOptions */
|
|
289
|
+
var options = {};
|
|
290
|
+
/** @type {EventListener} */
|
|
291
|
+
var event_handler;
|
|
292
|
+
|
|
293
|
+
if (typeof handler === 'object' && 'handleEvent' in handler) {
|
|
294
|
+
({ handleEvent: event_handler, ...options } = handler);
|
|
295
|
+
} else {
|
|
296
|
+
event_handler = handler;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return create_event(event_name, dom, event_handler, options);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Reactive version of event that automatically cleans up and re-attaches
|
|
304
|
+
* when the handler changes
|
|
305
|
+
* @param {string} event_name
|
|
306
|
+
* @param {Element} dom
|
|
307
|
+
* @param {() => EventListener | AddEventObject} get_handler
|
|
218
308
|
*/
|
|
219
|
-
export function
|
|
220
|
-
|
|
221
|
-
|
|
309
|
+
export function render_event(event_name, dom, get_handler) {
|
|
310
|
+
/** @type {EventListener | AddEventObject | undefined} */
|
|
311
|
+
var prev;
|
|
312
|
+
/** @type {(() => void) | undefined} */
|
|
313
|
+
var remove_listener;
|
|
314
|
+
|
|
315
|
+
render(() => {
|
|
316
|
+
var handler = get_handler();
|
|
317
|
+
|
|
318
|
+
if (handler !== prev) {
|
|
319
|
+
if (remove_listener) {
|
|
320
|
+
remove_listener();
|
|
321
|
+
remove_listener = undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
prev = handler;
|
|
325
|
+
|
|
326
|
+
if (handler) {
|
|
327
|
+
remove_listener = event(event_name, dom, handler);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
222
331
|
}
|
|
223
332
|
|
|
224
333
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @import { Block } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { destroy_block, ref } from './blocks.js';
|
|
3
|
+
import { branch, destroy_block, ref } from './blocks.js';
|
|
4
4
|
import { REF_PROP } from './constants.js';
|
|
5
5
|
import {
|
|
6
6
|
get_descriptors,
|
|
@@ -8,12 +8,8 @@ import {
|
|
|
8
8
|
get_prototype_of,
|
|
9
9
|
is_tracked_object,
|
|
10
10
|
} from './utils.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
get_attribute_event_name,
|
|
14
|
-
is_delegated,
|
|
15
|
-
is_event_attribute,
|
|
16
|
-
} from '../../../utils/events.js';
|
|
11
|
+
import { event } from './events.js';
|
|
12
|
+
import { get_attribute_event_name, is_event_attribute } from '../../../utils/events.js';
|
|
17
13
|
import { get } from './runtime.js';
|
|
18
14
|
import { clsx } from 'clsx';
|
|
19
15
|
import { normalize_css_property_name } from '../../../utils/normalize_css_property_name.js';
|
|
@@ -131,8 +127,9 @@ export function apply_styles(element, new_styles) {
|
|
|
131
127
|
* @param {Element} element
|
|
132
128
|
* @param {string} key
|
|
133
129
|
* @param {any} value
|
|
130
|
+
* @param {Record<string, (() => void) | undefined>} remove_listeners
|
|
134
131
|
*/
|
|
135
|
-
function set_attribute_helper(element, key, value) {
|
|
132
|
+
function set_attribute_helper(element, key, value, remove_listeners) {
|
|
136
133
|
if (key === 'class') {
|
|
137
134
|
const is_html = element.namespaceURI === 'http://www.w3.org/1999/xhtml';
|
|
138
135
|
set_class(/** @type {HTMLElement} */ (element), value, undefined, is_html);
|
|
@@ -143,16 +140,11 @@ function set_attribute_helper(element, key, value) {
|
|
|
143
140
|
element.classList.add(value);
|
|
144
141
|
} else if (typeof key === 'string' && is_event_attribute(key)) {
|
|
145
142
|
// Handle event handlers in spread props
|
|
146
|
-
const event_name = get_attribute_event_name(key);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Use delegation for delegated events
|
|
150
|
-
/** @type {any} */ (element)['__' + event_name] = value;
|
|
151
|
-
delegate([event_name]);
|
|
152
|
-
} else {
|
|
153
|
-
// Use addEventListener for non-delegated events
|
|
154
|
-
event(event_name, element, value);
|
|
143
|
+
const event_name = get_attribute_event_name(key, value);
|
|
144
|
+
if (remove_listeners[key]) {
|
|
145
|
+
remove_listeners[key]();
|
|
155
146
|
}
|
|
147
|
+
remove_listeners[key] = event(event_name, element, value);
|
|
156
148
|
} else {
|
|
157
149
|
set_attribute(element, key, value);
|
|
158
150
|
}
|
|
@@ -257,22 +249,25 @@ export function set_selected(element, selected) {
|
|
|
257
249
|
export function apply_element_spread(element, fn) {
|
|
258
250
|
/** @type {Record<string | symbol, any>} */
|
|
259
251
|
var prev = {};
|
|
260
|
-
/** @type {Record<symbol, Block>} */
|
|
252
|
+
/** @type {Record<symbol, Block | undefined>} */
|
|
261
253
|
var effects = {};
|
|
254
|
+
/** @type {Record<string | symbol, (() => void) | undefined>} */
|
|
255
|
+
var remove_listeners = {};
|
|
262
256
|
|
|
263
257
|
return () => {
|
|
264
258
|
var next = fn();
|
|
265
259
|
|
|
266
|
-
for (
|
|
267
|
-
if (!next[symbol]) {
|
|
260
|
+
for (const symbol of get_own_property_symbols(effects)) {
|
|
261
|
+
if (!next[symbol] && effects[symbol]) {
|
|
268
262
|
destroy_block(effects[symbol]);
|
|
263
|
+
effects[symbol] = undefined;
|
|
269
264
|
}
|
|
270
265
|
}
|
|
271
266
|
|
|
272
267
|
for (const symbol of get_own_property_symbols(next)) {
|
|
273
268
|
var ref_fn = next[symbol];
|
|
274
269
|
|
|
275
|
-
if (symbol.description === REF_PROP && (!prev || ref_fn !== prev[symbol])) {
|
|
270
|
+
if (symbol.description === REF_PROP && (!(symbol in prev) || ref_fn !== prev[symbol])) {
|
|
276
271
|
if (effects[symbol]) {
|
|
277
272
|
destroy_block(effects[symbol]);
|
|
278
273
|
}
|
|
@@ -282,7 +277,24 @@ export function apply_element_spread(element, fn) {
|
|
|
282
277
|
next[symbol] = ref_fn;
|
|
283
278
|
}
|
|
284
279
|
|
|
285
|
-
|
|
280
|
+
for (let key in remove_listeners) {
|
|
281
|
+
// Remove event listeners that are no longer present
|
|
282
|
+
if (!(key in next) && remove_listeners[key]) {
|
|
283
|
+
remove_listeners[key]();
|
|
284
|
+
remove_listeners[key] = undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const key in prev) {
|
|
289
|
+
if (!(key in next)) {
|
|
290
|
+
if (key === '#class') {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
set_attribute_helper(element, key, null, remove_listeners);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** @type {typeof prev} */
|
|
286
298
|
const current = {};
|
|
287
299
|
for (const key in next) {
|
|
288
300
|
if (key === 'children') continue;
|
|
@@ -293,13 +305,11 @@ export function apply_element_spread(element, fn) {
|
|
|
293
305
|
}
|
|
294
306
|
current[key] = value;
|
|
295
307
|
|
|
296
|
-
if (
|
|
297
|
-
prev[key] = value;
|
|
298
|
-
} else if (key !== '#class') {
|
|
308
|
+
if (key in prev && prev[key] === value && key !== '#class') {
|
|
299
309
|
continue;
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
set_attribute_helper(element, key, value);
|
|
312
|
+
set_attribute_helper(element, key, value, remove_listeners);
|
|
303
313
|
}
|
|
304
314
|
prev = current;
|
|
305
315
|
};
|
|
@@ -20,8 +20,8 @@ export function MediaQuery(query, fallback) {
|
|
|
20
20
|
|
|
21
21
|
let final_query =
|
|
22
22
|
parenthesis_regex.test(query) ||
|
|
23
|
-
|
|
24
|
-
|
|
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
25
|
? query
|
|
26
26
|
: `(${query})`;
|
|
27
27
|
const q = window.matchMedia(final_query);
|
|
@@ -29,11 +29,17 @@ export function MediaQuery(query, fallback) {
|
|
|
29
29
|
|
|
30
30
|
return new ReactiveValue(
|
|
31
31
|
() => get(matches),
|
|
32
|
-
() =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
() =>
|
|
33
|
+
on(
|
|
34
|
+
q,
|
|
35
|
+
'change',
|
|
36
|
+
() => {
|
|
37
|
+
// skip wrapping in untrack as createSubscriber already does it
|
|
38
|
+
if (q.matches !== get(matches)) {
|
|
39
|
+
set(matches, q.matches);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ delegated: false },
|
|
43
|
+
),
|
|
38
44
|
);
|
|
39
45
|
}
|