ripple 0.2.91 → 0.2.92
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/index.js +14 -8
- package/src/compiler/phases/1-parse/index.js +55 -7
- package/src/compiler/phases/3-transform/client/index.js +81 -49
- package/src/compiler/phases/3-transform/segments.js +57 -3
- package/src/compiler/scope.js +478 -404
- package/src/compiler/types/index.d.ts +299 -3
- package/src/compiler/utils.js +173 -30
- package/src/runtime/index-client.js +1 -0
- package/src/runtime/internal/client/html.js +18 -8
- package/src/runtime/internal/client/index.js +1 -0
- package/src/runtime/internal/client/portal.js +55 -32
- package/src/runtime/internal/client/render.js +31 -1
- package/src/runtime/internal/client/runtime.js +53 -22
- package/src/utils/normalize_css_property_name.js +23 -0
- package/tests/client/basic.test.ripple +207 -1
- package/tests/client/compiler.test.ripple +95 -1
- package/tests/client/html.test.ripple +29 -1
- package/tests/client/portal.test.ripple +167 -0
- package/types/index.d.ts +2 -0
package/src/compiler/utils.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
/** @import { Identifier, Pattern, Super, FunctionExpression, FunctionDeclaration, ArrowFunctionExpression, MemberExpression, AssignmentExpression, Expression, Node, AssignmentOperator } from 'estree' */
|
|
2
|
+
/** @import { Component, Element, Attribute, SpreadAttribute, ScopeInterface, Binding, RippleNode, CompilerState, TransformContext, DelegatedEventResult, TextNode } from '#compiler' */
|
|
3
|
+
import { build_assignment_value, extract_paths } from '../utils/ast.js';
|
|
2
4
|
import * as b from '../utils/builders.js';
|
|
3
5
|
import { get_attribute_event_name, is_delegated, is_event_attribute } from '../utils/events.js';
|
|
4
6
|
|
|
@@ -27,6 +29,11 @@ const VOID_ELEMENT_NAMES = [
|
|
|
27
29
|
* Returns `true` if `name` is of a void element
|
|
28
30
|
* @param {string} name
|
|
29
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* Returns true if name is a void element
|
|
34
|
+
* @param {string} name
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
30
37
|
export function is_void_element(name) {
|
|
31
38
|
return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
|
|
32
39
|
}
|
|
@@ -82,6 +89,11 @@ const RESERVED_WORDS = [
|
|
|
82
89
|
'yield',
|
|
83
90
|
];
|
|
84
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Returns true if word is a reserved JS keyword
|
|
94
|
+
* @param {string} word
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
85
97
|
export function is_reserved(word) {
|
|
86
98
|
return RESERVED_WORDS.includes(word);
|
|
87
99
|
}
|
|
@@ -121,6 +133,11 @@ const DOM_BOOLEAN_ATTRIBUTES = [
|
|
|
121
133
|
'disableremoteplayback',
|
|
122
134
|
];
|
|
123
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Returns true if name is a boolean DOM attribute
|
|
138
|
+
* @param {string} name
|
|
139
|
+
* @returns {boolean}
|
|
140
|
+
*/
|
|
124
141
|
export function is_boolean_attribute(name) {
|
|
125
142
|
return DOM_BOOLEAN_ATTRIBUTES.includes(name);
|
|
126
143
|
}
|
|
@@ -143,12 +160,24 @@ const DOM_PROPERTIES = [
|
|
|
143
160
|
'disableRemotePlayback',
|
|
144
161
|
];
|
|
145
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Returns true if name is a DOM property
|
|
165
|
+
* @param {string} name
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
146
168
|
export function is_dom_property(name) {
|
|
147
169
|
return DOM_PROPERTIES.includes(name);
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
const unhoisted = { hoisted: false };
|
|
151
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Determines if an event handler can be hoisted for delegation
|
|
176
|
+
* @param {string} event_name
|
|
177
|
+
* @param {Expression} handler
|
|
178
|
+
* @param {CompilerState} state
|
|
179
|
+
* @returns {DelegatedEventResult | null}
|
|
180
|
+
*/
|
|
152
181
|
export function get_delegated_event(event_name, handler, state) {
|
|
153
182
|
// Handle delegated event handlers. Bail out if not a delegated event.
|
|
154
183
|
if (!handler || !is_delegated(event_name)) {
|
|
@@ -176,18 +205,18 @@ export function get_delegated_event(event_name, handler, state) {
|
|
|
176
205
|
|
|
177
206
|
const grandparent = path.at(-2);
|
|
178
207
|
|
|
179
|
-
/** @type {
|
|
208
|
+
/** @type {Element | null} */
|
|
180
209
|
let element = null;
|
|
181
210
|
/** @type {string | null} */
|
|
182
211
|
let event_name = null;
|
|
183
212
|
if (
|
|
184
|
-
parent.type === '
|
|
213
|
+
parent.type === 'Expression' &&
|
|
185
214
|
grandparent?.type === 'Attribute' &&
|
|
186
215
|
is_event_attribute(grandparent)
|
|
187
216
|
) {
|
|
188
|
-
element = /** @type {
|
|
189
|
-
const attribute = /** @type {
|
|
190
|
-
event_name = get_attribute_event_name(attribute.name);
|
|
217
|
+
element = /** @type {Element} */ (path.at(-3));
|
|
218
|
+
const attribute = /** @type {Attribute} */ (grandparent);
|
|
219
|
+
event_name = get_attribute_event_name(attribute.name.name);
|
|
191
220
|
}
|
|
192
221
|
|
|
193
222
|
if (element && event_name) {
|
|
@@ -205,7 +234,11 @@ export function get_delegated_event(event_name, handler, state) {
|
|
|
205
234
|
}
|
|
206
235
|
|
|
207
236
|
// If the binding is exported, bail out
|
|
208
|
-
if (
|
|
237
|
+
if (
|
|
238
|
+
state.analysis?.exports?.find(
|
|
239
|
+
(/** @type {{name: string}} */ node) => node.name === handler.name,
|
|
240
|
+
)
|
|
241
|
+
) {
|
|
209
242
|
return unhoisted;
|
|
210
243
|
}
|
|
211
244
|
|
|
@@ -256,6 +289,11 @@ export function get_delegated_event(event_name, handler, state) {
|
|
|
256
289
|
return { hoisted: true, function: target_function };
|
|
257
290
|
}
|
|
258
291
|
|
|
292
|
+
/**
|
|
293
|
+
* @param {Node} node
|
|
294
|
+
* @param {TransformContext} context
|
|
295
|
+
* @returns {Identifier[]}
|
|
296
|
+
*/
|
|
259
297
|
function get_hoisted_params(node, context) {
|
|
260
298
|
const scope = context.state.scope;
|
|
261
299
|
|
|
@@ -292,6 +330,12 @@ function get_hoisted_params(node, context) {
|
|
|
292
330
|
return params;
|
|
293
331
|
}
|
|
294
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Builds the parameter list for a hoisted function
|
|
335
|
+
* @param {FunctionDeclaration|FunctionExpression|ArrowFunctionExpression} node
|
|
336
|
+
* @param {TransformContext} context
|
|
337
|
+
* @returns {Pattern[]}
|
|
338
|
+
*/
|
|
295
339
|
export function build_hoisted_params(node, context) {
|
|
296
340
|
const hoisted_params = get_hoisted_params(node, context);
|
|
297
341
|
node.metadata.hoisted_params = hoisted_params;
|
|
@@ -314,6 +358,11 @@ export function build_hoisted_params(node, context) {
|
|
|
314
358
|
return params;
|
|
315
359
|
}
|
|
316
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Returns true if context is inside a top-level await
|
|
363
|
+
* @param {TransformContext} context
|
|
364
|
+
* @returns {boolean}
|
|
365
|
+
*/
|
|
317
366
|
export function is_top_level_await(context) {
|
|
318
367
|
if (!is_inside_component(context)) {
|
|
319
368
|
return false;
|
|
@@ -323,7 +372,7 @@ export function is_top_level_await(context) {
|
|
|
323
372
|
const context_node = context.path[i];
|
|
324
373
|
const type = context_node.type;
|
|
325
374
|
|
|
326
|
-
if (type === 'Component') {
|
|
375
|
+
if (/** @type {Component} */ (context_node).type === 'Component') {
|
|
327
376
|
return true;
|
|
328
377
|
}
|
|
329
378
|
|
|
@@ -338,6 +387,12 @@ export function is_top_level_await(context) {
|
|
|
338
387
|
return true;
|
|
339
388
|
}
|
|
340
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Returns true if context is inside a Component node
|
|
392
|
+
* @param {TransformContext} context
|
|
393
|
+
* @param {boolean} [includes_functions=false]
|
|
394
|
+
* @returns {boolean}
|
|
395
|
+
*/
|
|
341
396
|
export function is_inside_component(context, includes_functions = false) {
|
|
342
397
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
343
398
|
const context_node = context.path[i];
|
|
@@ -351,13 +406,18 @@ export function is_inside_component(context, includes_functions = false) {
|
|
|
351
406
|
) {
|
|
352
407
|
return false;
|
|
353
408
|
}
|
|
354
|
-
if (type === 'Component') {
|
|
409
|
+
if (/** @type {Component} */ (context_node).type === 'Component') {
|
|
355
410
|
return true;
|
|
356
411
|
}
|
|
357
412
|
}
|
|
358
413
|
return false;
|
|
359
414
|
}
|
|
360
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Returns true if context is inside a component-level function
|
|
418
|
+
* @param {TransformContext} context
|
|
419
|
+
* @returns {boolean}
|
|
420
|
+
*/
|
|
361
421
|
export function is_component_level_function(context) {
|
|
362
422
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
363
423
|
const context_node = context.path[i];
|
|
@@ -378,18 +438,32 @@ export function is_component_level_function(context) {
|
|
|
378
438
|
return true;
|
|
379
439
|
}
|
|
380
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Returns true if callee is a Ripple track call
|
|
443
|
+
* @param {Expression | Super} callee
|
|
444
|
+
* @param {TransformContext} context
|
|
445
|
+
* @returns {boolean}
|
|
446
|
+
*/
|
|
381
447
|
export function is_ripple_track_call(callee, context) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
448
|
+
// Super expressions cannot be Ripple track calls
|
|
449
|
+
if (callee.type === 'Super') return false;
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
(callee.type === 'Identifier' && (callee.name === 'track' || callee.name === 'trackSplit')) ||
|
|
453
|
+
(callee.type === 'MemberExpression' &&
|
|
454
|
+
callee.object.type === 'Identifier' &&
|
|
455
|
+
callee.property.type === 'Identifier' &&
|
|
456
|
+
(callee.property.name === 'track' || callee.property.name === 'trackSplit') &&
|
|
457
|
+
!callee.computed &&
|
|
458
|
+
is_ripple_import(callee, context))
|
|
459
|
+
);
|
|
391
460
|
}
|
|
392
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Returns true if context is inside a call expression
|
|
464
|
+
* @param {TransformContext} context
|
|
465
|
+
* @returns {boolean}
|
|
466
|
+
*/
|
|
393
467
|
export function is_inside_call_expression(context) {
|
|
394
468
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
395
469
|
const context_node = context.path[i];
|
|
@@ -413,6 +487,11 @@ export function is_inside_call_expression(context) {
|
|
|
413
487
|
return false;
|
|
414
488
|
}
|
|
415
489
|
|
|
490
|
+
/**
|
|
491
|
+
* Returns true if node is a static value (Literal, ArrayExpression, etc)
|
|
492
|
+
* @param {Node} node
|
|
493
|
+
* @returns {boolean}
|
|
494
|
+
*/
|
|
416
495
|
export function is_value_static(node) {
|
|
417
496
|
if (node.type === 'Literal') {
|
|
418
497
|
return true;
|
|
@@ -430,12 +509,20 @@ export function is_value_static(node) {
|
|
|
430
509
|
return false;
|
|
431
510
|
}
|
|
432
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Returns true if callee is a Ripple import
|
|
514
|
+
* @param {Expression} callee
|
|
515
|
+
* @param {TransformContext} context
|
|
516
|
+
* @returns {boolean}
|
|
517
|
+
*/
|
|
433
518
|
export function is_ripple_import(callee, context) {
|
|
434
519
|
if (callee.type === 'Identifier') {
|
|
435
520
|
const binding = context.state.scope.get(callee.name);
|
|
436
521
|
|
|
437
522
|
return (
|
|
438
523
|
binding?.declaration_kind === 'import' &&
|
|
524
|
+
binding.initial !== null &&
|
|
525
|
+
binding.initial.type === 'ImportDeclaration' &&
|
|
439
526
|
binding.initial.source.type === 'Literal' &&
|
|
440
527
|
binding.initial.source.value === 'ripple'
|
|
441
528
|
);
|
|
@@ -448,6 +535,8 @@ export function is_ripple_import(callee, context) {
|
|
|
448
535
|
|
|
449
536
|
return (
|
|
450
537
|
binding?.declaration_kind === 'import' &&
|
|
538
|
+
binding.initial !== null &&
|
|
539
|
+
binding.initial.type === 'ImportDeclaration' &&
|
|
451
540
|
binding.initial.source.type === 'Literal' &&
|
|
452
541
|
binding.initial.source.value === 'ripple'
|
|
453
542
|
);
|
|
@@ -456,8 +545,14 @@ export function is_ripple_import(callee, context) {
|
|
|
456
545
|
return false;
|
|
457
546
|
}
|
|
458
547
|
|
|
548
|
+
/**
|
|
549
|
+
* Returns true if node is a function declared within a component
|
|
550
|
+
* @param {import('estree').Identifier} node
|
|
551
|
+
* @param {TransformContext} context
|
|
552
|
+
* @returns {boolean}
|
|
553
|
+
*/
|
|
459
554
|
export function is_declared_function_within_component(node, context) {
|
|
460
|
-
const component = context.path
|
|
555
|
+
const component = context.path?.find(/** @param {RippleNode} n */ (n) => n.type === 'Component');
|
|
461
556
|
|
|
462
557
|
if (node.type === 'Identifier' && component) {
|
|
463
558
|
const binding = context.state.scope.get(node.name);
|
|
@@ -485,11 +580,13 @@ export function is_declared_function_within_component(node, context) {
|
|
|
485
580
|
|
|
486
581
|
return false;
|
|
487
582
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
583
|
+
/**
|
|
584
|
+
* Visits and transforms an assignment expression
|
|
585
|
+
* @param {AssignmentExpression} node
|
|
586
|
+
* @param {TransformContext} context
|
|
587
|
+
* @param {Function} build_assignment
|
|
588
|
+
* @returns {Expression | AssignmentExpression | null}
|
|
589
|
+
*/
|
|
493
590
|
export function visit_assignment_expression(node, context, build_assignment) {
|
|
494
591
|
if (
|
|
495
592
|
node.left.type === 'ArrayPattern' ||
|
|
@@ -523,7 +620,7 @@ export function visit_assignment_expression(node, context, build_assignment) {
|
|
|
523
620
|
return null;
|
|
524
621
|
}
|
|
525
622
|
|
|
526
|
-
const is_standalone =
|
|
623
|
+
const is_standalone = context.path.at(-1).type.endsWith('Statement');
|
|
527
624
|
const sequence = b.sequence(assignments);
|
|
528
625
|
|
|
529
626
|
if (!is_standalone) {
|
|
@@ -535,11 +632,7 @@ export function visit_assignment_expression(node, context, build_assignment) {
|
|
|
535
632
|
// the right hand side is a complex expression, wrap in an IIFE to cache it
|
|
536
633
|
const iife = b.arrow([rhs], sequence);
|
|
537
634
|
|
|
538
|
-
|
|
539
|
-
is_expression_async(value) ||
|
|
540
|
-
assignments.some((assignment) => is_expression_async(assignment));
|
|
541
|
-
|
|
542
|
-
return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
|
|
635
|
+
return b.call(iife, value);
|
|
543
636
|
}
|
|
544
637
|
|
|
545
638
|
return sequence;
|
|
@@ -558,6 +651,14 @@ export function visit_assignment_expression(node, context, build_assignment) {
|
|
|
558
651
|
return transformed;
|
|
559
652
|
}
|
|
560
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Builds an assignment node, possibly transforming for reactivity
|
|
656
|
+
* @param {AssignmentOperator} operator
|
|
657
|
+
* @param {Pattern | MemberExpression | Identifier} left
|
|
658
|
+
* @param {Expression} right
|
|
659
|
+
* @param {TransformContext} context
|
|
660
|
+
* @returns {Expression|null}
|
|
661
|
+
*/
|
|
561
662
|
export function build_assignment(operator, left, right, context) {
|
|
562
663
|
let object = left;
|
|
563
664
|
|
|
@@ -611,6 +712,12 @@ export function build_assignment(operator, left, right, context) {
|
|
|
611
712
|
const ATTR_REGEX = /[&"<]/g;
|
|
612
713
|
const CONTENT_REGEX = /[&<]/g;
|
|
613
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Escapes HTML special characters in a string
|
|
717
|
+
* @param {string} value
|
|
718
|
+
* @param {boolean} [is_attr=false]
|
|
719
|
+
* @returns {string}
|
|
720
|
+
*/
|
|
614
721
|
export function escape_html(value, is_attr = false) {
|
|
615
722
|
const str = String(value ?? '');
|
|
616
723
|
|
|
@@ -630,6 +737,11 @@ export function escape_html(value, is_attr = false) {
|
|
|
630
737
|
return escaped + str.substring(last);
|
|
631
738
|
}
|
|
632
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Hashes a string to a base36 value
|
|
742
|
+
* @param {string} str
|
|
743
|
+
* @returns {string}
|
|
744
|
+
*/
|
|
633
745
|
export function hash(str) {
|
|
634
746
|
str = str.replace(regex_return_characters, '');
|
|
635
747
|
let hash = 5381;
|
|
@@ -639,6 +751,11 @@ export function hash(str) {
|
|
|
639
751
|
return (hash >>> 0).toString(36);
|
|
640
752
|
}
|
|
641
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Returns true if node is a DOM element (not a component)
|
|
756
|
+
* @param {Element} node
|
|
757
|
+
* @returns {boolean}
|
|
758
|
+
*/
|
|
642
759
|
export function is_element_dom_element(node) {
|
|
643
760
|
return (
|
|
644
761
|
node.id.type === 'Identifier' &&
|
|
@@ -648,7 +765,14 @@ export function is_element_dom_element(node) {
|
|
|
648
765
|
);
|
|
649
766
|
}
|
|
650
767
|
|
|
768
|
+
/**
|
|
769
|
+
* Normalizes children nodes (merges adjacent text, removes empty)
|
|
770
|
+
* @param {RippleNode[]} children
|
|
771
|
+
* @param {TransformContext} context
|
|
772
|
+
* @returns {RippleNode[]}
|
|
773
|
+
*/
|
|
651
774
|
export function normalize_children(children, context) {
|
|
775
|
+
/** @type {RippleNode[]} */
|
|
652
776
|
const normalized = [];
|
|
653
777
|
|
|
654
778
|
for (const node of children) {
|
|
@@ -678,6 +802,11 @@ export function normalize_children(children, context) {
|
|
|
678
802
|
return normalized;
|
|
679
803
|
}
|
|
680
804
|
|
|
805
|
+
/**
|
|
806
|
+
* @param {RippleNode} node
|
|
807
|
+
* @param {RippleNode[]} normalized
|
|
808
|
+
* @param {TransformContext} context
|
|
809
|
+
*/
|
|
681
810
|
function normalize_child(node, normalized, context) {
|
|
682
811
|
if (node.type === 'EmptyStatement') {
|
|
683
812
|
return;
|
|
@@ -694,9 +823,17 @@ function normalize_child(node, normalized, context) {
|
|
|
694
823
|
}
|
|
695
824
|
}
|
|
696
825
|
|
|
826
|
+
/**
|
|
827
|
+
* Builds a getter for a tracked identifier
|
|
828
|
+
* @param {Identifier} node
|
|
829
|
+
* @param {TransformContext} context
|
|
830
|
+
* @returns {Expression | Identifier}
|
|
831
|
+
*/
|
|
697
832
|
export function build_getter(node, context) {
|
|
698
833
|
const state = context.state;
|
|
699
834
|
|
|
835
|
+
if (!context.path) return node;
|
|
836
|
+
|
|
700
837
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
701
838
|
const binding = state.scope.get(node.name);
|
|
702
839
|
const transform = binding?.transform;
|
|
@@ -714,6 +851,12 @@ export function build_getter(node, context) {
|
|
|
714
851
|
return node;
|
|
715
852
|
}
|
|
716
853
|
|
|
854
|
+
/**
|
|
855
|
+
* Determines the namespace for child elements
|
|
856
|
+
* @param {string} element_name
|
|
857
|
+
* @param {string} current_namespace
|
|
858
|
+
* @returns {string}
|
|
859
|
+
*/
|
|
717
860
|
export function determine_namespace_for_children(element_name, current_namespace) {
|
|
718
861
|
if (element_name === 'foreignObject') {
|
|
719
862
|
return 'html';
|
|
@@ -8,14 +8,11 @@ import { assign_nodes, create_fragment_from_html } from './template.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Renders dynamic HTML content into the DOM by inserting it before the anchor node.
|
|
10
10
|
* Manages the lifecycle of HTML blocks, removing old content and inserting new content.
|
|
11
|
-
*
|
|
12
|
-
* TODO handle SVG/MathML
|
|
13
|
-
*
|
|
14
11
|
* @param {ChildNode} node
|
|
15
12
|
* @param {() => string} get_html
|
|
16
13
|
* @returns {void}
|
|
17
14
|
*/
|
|
18
|
-
export function html(node, get_html) {
|
|
15
|
+
export function html(node, get_html, svg = false, mathml = false) {
|
|
19
16
|
/** @type {ChildNode} */
|
|
20
17
|
var anchor = node;
|
|
21
18
|
/** @type {string} */
|
|
@@ -25,17 +22,30 @@ export function html(node, get_html) {
|
|
|
25
22
|
var block = /** @type {Block} */ (active_block);
|
|
26
23
|
html = get_html() + '';
|
|
27
24
|
|
|
25
|
+
if (svg) html = `<svg>${html}</svg>`;
|
|
26
|
+
else if (mathml) html = `<math>${html}</math>`;
|
|
27
|
+
|
|
28
28
|
if (block.s !== null && block.s.start !== null) {
|
|
29
|
-
remove_block_dom(block.s.start, /** @type {Node} */
|
|
29
|
+
remove_block_dom(block.s.start, /** @type {Node} */(block.s.end));
|
|
30
30
|
block.s.start = block.s.end = null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (html === '') return;
|
|
34
|
-
/** @type {DocumentFragment} */
|
|
34
|
+
/** @type {DocumentFragment | Element} */
|
|
35
35
|
var node = create_fragment_from_html(html);
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
if (svg || mathml) {
|
|
38
|
+
node = /** @type {Element} */(first_child(node));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
assign_nodes(/** @type {Element} */(first_child(node)), /** @type {Element} */(node.lastChild));
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
if (svg || mathml) {
|
|
44
|
+
while (first_child(node)) {
|
|
45
|
+
anchor.before(/** @type {Element} */(first_child(node)));
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
anchor.before(node);
|
|
49
|
+
}
|
|
40
50
|
});
|
|
41
51
|
}
|
|
@@ -1,43 +1,66 @@
|
|
|
1
1
|
/** @import { Block } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { branch, destroy_block, render } from './blocks.js';
|
|
3
|
+
import { branch, destroy_block, remove_block_dom, render } from './blocks.js';
|
|
4
4
|
import { UNINITIALIZED } from './constants.js';
|
|
5
5
|
import { handle_root_events } from './events.js';
|
|
6
6
|
import { create_text } from './operations.js';
|
|
7
|
+
import { active_block } from './runtime.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* @param {any} _
|
|
10
|
-
* @param {{ target: Element, children: (anchor: Node) => void }} props
|
|
10
|
+
* @param {any} _
|
|
11
|
+
* @param {{ target: Element, children: (anchor: Node, props: {}, block: Block) => void }} props
|
|
11
12
|
* @returns {void}
|
|
12
13
|
*/
|
|
13
14
|
export function Portal(_, props) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
15
|
+
/** @type {Element | symbol} */
|
|
16
|
+
let target = UNINITIALIZED;
|
|
17
|
+
/** @type {((anchor: Node, props: {}, block: Block) => void) | symbol} */
|
|
18
|
+
let children = UNINITIALIZED;
|
|
19
|
+
/** @type {Block | null} */
|
|
20
|
+
var b = null;
|
|
21
|
+
/** @type {Text | null} */
|
|
22
|
+
var anchor = null;
|
|
23
|
+
/** @type {Node | null} */
|
|
24
|
+
var dom_start = null;
|
|
25
|
+
/** @type {Node | null} */
|
|
26
|
+
var dom_end = null;
|
|
27
|
+
|
|
28
|
+
render(() => {
|
|
29
|
+
if (target === (target = props.target)) return;
|
|
30
|
+
if (children === (children = props.children)) return;
|
|
31
|
+
|
|
32
|
+
if (b !== null) {
|
|
33
|
+
destroy_block(b);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (anchor !== null) {
|
|
37
|
+
anchor.remove();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
dom_start = dom_end = null;
|
|
41
|
+
|
|
42
|
+
anchor = create_text();
|
|
43
|
+
/** @type {Element} */ (target).append(anchor);
|
|
44
|
+
|
|
45
|
+
const cleanup_events = handle_root_events(/** @type {Element} */ (target));
|
|
46
|
+
|
|
47
|
+
var block = /** @type {Block} */ (active_block);
|
|
48
|
+
|
|
49
|
+
b = branch(() => {
|
|
50
|
+
if (typeof children === 'function') {
|
|
51
|
+
children(/** @type {Text} */ (anchor), {}, block);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
dom_start = b?.s?.start;
|
|
56
|
+
dom_end = b?.s?.end;
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
cleanup_events();
|
|
60
|
+
/** @type {Text} */ (anchor).remove();
|
|
61
|
+
if (dom_start && dom_end) {
|
|
62
|
+
remove_block_dom(dom_start, dom_end);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
});
|
|
43
66
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '../../../utils/events.js';
|
|
17
17
|
import { get } from './runtime.js';
|
|
18
18
|
import { clsx } from 'clsx';
|
|
19
|
+
import { normalize_css_property_name } from '../../../utils/normalize_css_property_name.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @param {Text} text
|
|
@@ -85,13 +86,42 @@ export function set_attribute(element, attribute, value) {
|
|
|
85
86
|
|
|
86
87
|
if (value == null) {
|
|
87
88
|
element.removeAttribute(attribute);
|
|
88
|
-
} else if (typeof value !== 'string'
|
|
89
|
+
} else if (attribute === 'style' && typeof value !== 'string') {
|
|
90
|
+
apply_styles(/** @type {HTMLElement} */ (element), value);
|
|
91
|
+
} else if (typeof value !== 'string' && get_setters(element).includes(attribute)) {
|
|
89
92
|
/** @type {any} */ (element)[attribute] = value;
|
|
90
93
|
} else {
|
|
91
94
|
element.setAttribute(attribute, value);
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
/**
|
|
99
|
+
* @param {HTMLElement} element
|
|
100
|
+
* @param {HTMLElement['style']} newStyles
|
|
101
|
+
*/
|
|
102
|
+
export function apply_styles(element, newStyles) {
|
|
103
|
+
const style = element.style;
|
|
104
|
+
const new_properties = new Set();
|
|
105
|
+
|
|
106
|
+
for(const [property, value] of Object.entries(newStyles)) {
|
|
107
|
+
const normalized_property = normalize_css_property_name(property);
|
|
108
|
+
const normalized_value = String(value);
|
|
109
|
+
|
|
110
|
+
if (style.getPropertyValue(normalized_property) !== normalized_value) {
|
|
111
|
+
style.setProperty(normalized_property, normalized_value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
new_properties.add(normalized_property);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (let i = style.length - 1; i >= 0; i--) {
|
|
118
|
+
const property = style[i];
|
|
119
|
+
if (!new_properties.has(property)) {
|
|
120
|
+
style.removeProperty(property);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
95
125
|
/**
|
|
96
126
|
* @param {Element} element
|
|
97
127
|
* @param {Record<string, any>} attributes
|