ripple 0.2.115 → 0.2.118

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.
Files changed (93) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/1-parse/index.js +79 -0
  4. package/src/compiler/phases/3-transform/client/index.js +54 -8
  5. package/src/compiler/phases/3-transform/segments.js +107 -60
  6. package/src/compiler/phases/3-transform/server/index.js +21 -11
  7. package/src/compiler/types/index.d.ts +16 -0
  8. package/src/runtime/index-client.js +19 -185
  9. package/src/runtime/index-server.js +24 -0
  10. package/src/runtime/internal/client/bindings.js +443 -0
  11. package/src/runtime/internal/client/index.js +4 -0
  12. package/src/runtime/internal/client/runtime.js +10 -0
  13. package/src/runtime/internal/client/utils.js +0 -8
  14. package/src/runtime/map.js +11 -1
  15. package/src/runtime/set.js +11 -1
  16. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  17. package/tests/client/_etc.test.ripple +5 -0
  18. package/tests/client/array/array.copy-within.test.ripple +120 -0
  19. package/tests/client/array/array.derived.test.ripple +495 -0
  20. package/tests/client/array/array.iteration.test.ripple +115 -0
  21. package/tests/client/array/array.mutations.test.ripple +385 -0
  22. package/tests/client/array/array.static.test.ripple +237 -0
  23. package/tests/client/array/array.to-methods.test.ripple +93 -0
  24. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  26. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  27. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  28. package/tests/client/basic/basic.collections.test.ripple +94 -0
  29. package/tests/client/basic/basic.components.test.ripple +225 -0
  30. package/tests/client/basic/basic.errors.test.ripple +126 -0
  31. package/tests/client/basic/basic.events.test.ripple +222 -0
  32. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  33. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  34. package/tests/client/basic/basic.styling.test.ripple +63 -0
  35. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  36. package/tests/client/boundaries.test.ripple +2 -21
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  39. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  40. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  41. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  42. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  43. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  44. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  45. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  46. package/tests/client/composite/composite.generics.test.ripple +211 -0
  47. package/tests/client/composite/composite.props.test.ripple +106 -0
  48. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  49. package/tests/client/composite/composite.render.test.ripple +84 -0
  50. package/tests/client/computed-properties.test.ripple +2 -21
  51. package/tests/client/context.test.ripple +5 -22
  52. package/tests/client/date.test.ripple +1 -20
  53. package/tests/client/dynamic-elements.test.ripple +16 -24
  54. package/tests/client/for.test.ripple +4 -23
  55. package/tests/client/head.test.ripple +11 -23
  56. package/tests/client/html.test.ripple +1 -20
  57. package/tests/client/input-value.test.ripple +11 -31
  58. package/tests/client/map.test.ripple +82 -20
  59. package/tests/client/media-query.test.ripple +10 -23
  60. package/tests/client/object.test.ripple +5 -24
  61. package/tests/client/portal.test.ripple +2 -19
  62. package/tests/client/ref.test.ripple +8 -26
  63. package/tests/client/set.test.ripple +84 -22
  64. package/tests/client/svg.test.ripple +1 -22
  65. package/tests/client/switch.test.ripple +6 -25
  66. package/tests/client/tracked-expression.test.ripple +2 -21
  67. package/tests/client/typescript-generics.test.ripple +0 -21
  68. package/tests/client/url/url.derived.test.ripple +83 -0
  69. package/tests/client/url/url.parsing.test.ripple +165 -0
  70. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  71. package/tests/client/url/url.reactivity.test.ripple +449 -0
  72. package/tests/client/url/url.serialization.test.ripple +50 -0
  73. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  74. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  75. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  76. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  77. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  78. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  79. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  80. package/tests/client.d.ts +12 -0
  81. package/tests/server/if.test.ripple +66 -0
  82. package/tests/setup-client.js +28 -0
  83. package/tsconfig.json +4 -2
  84. package/types/index.d.ts +92 -46
  85. package/LICENSE +0 -21
  86. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  87. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  88. package/tests/client/array.test.ripple +0 -1455
  89. package/tests/client/basic.test.ripple +0 -1892
  90. package/tests/client/compiler.test.ripple +0 -541
  91. package/tests/client/composite.test.ripple +0 -692
  92. package/tests/client/url-search-params.test.ripple +0 -912
  93. package/tests/client/url.test.ripple +0 -954
@@ -1,10 +1,10 @@
1
- /** @import { Block, Tracked } from '#client' */
1
+ /** @import { Block } from '#client' */
2
2
 
3
- import { destroy_block, effect, render, root } from './internal/client/blocks.js';
4
- import { handle_root_events, on } from './internal/client/events.js';
3
+ import { destroy_block, root } from './internal/client/blocks.js';
4
+ import { handle_root_events } from './internal/client/events.js';
5
5
  import { init_operations } from './internal/client/operations.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';
6
+ import { active_block } from './internal/client/runtime.js';
7
+ import { create_anchor } 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"
@@ -74,186 +74,20 @@ export { user_effect as effect } from './internal/client/blocks.js';
74
74
 
75
75
  export { Portal } from './internal/client/portal.js';
76
76
 
77
- export { ref_prop as createRefKey } from './internal/client/runtime.js';
77
+ export { ref_prop as createRefKey, get, public_set as set } from './internal/client/runtime.js';
78
78
 
79
79
  export { on } from './internal/client/events.js';
80
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
- }
81
+ export {
82
+ bindValue,
83
+ bindChecked,
84
+ bindClientWidth,
85
+ bindClientHeight,
86
+ bindContentRect,
87
+ bindContentBoxSize,
88
+ bindBorderBoxSize,
89
+ bindDevicePixelContentBoxSize,
90
+ bindInnerHTML,
91
+ bindInnerText,
92
+ bindTextContent,
93
+ } from './internal/client/bindings.js';
@@ -12,6 +12,30 @@ export function effect() {
12
12
 
13
13
  var empty_get_set = { get: undefined, set: undefined };
14
14
 
15
+ /**
16
+ * @param {Derived | Tracked} tracked
17
+ * @returns {any}
18
+ */
19
+ export function get(tracked) {
20
+ if (!is_tracked_object(tracked)) {
21
+ return tracked;
22
+ }
23
+
24
+ var g = tracked.a.get;
25
+
26
+ return g ? g(tracked.v) : tracked.v;
27
+ }
28
+
29
+ /**
30
+ * @param {Tracked} tracked
31
+ * @param {any} value
32
+ */
33
+ export function set(tracked, value) {
34
+ var s = tracked.a.set;
35
+
36
+ tracked.v = s ? s(value, tracked.v) : value;
37
+ }
38
+
15
39
  /**
16
40
  *
17
41
  * @param {any} v
@@ -0,0 +1,443 @@
1
+ /** @import { Block, Tracked } from '#client' */
2
+
3
+ import { effect, render } from './blocks.js';
4
+ import { on } from './events.js';
5
+ import { active_block, get, set, tick, untrack } from './runtime.js';
6
+ import { is_array, is_tracked_object } from './utils.js';
7
+
8
+ /**
9
+ * Resize observer singleton.
10
+ * One listener per element only!
11
+ * https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ
12
+ */
13
+ class ResizeObserverSingleton {
14
+ /** */
15
+ #listeners = new WeakMap();
16
+
17
+ /** @type {ResizeObserver | undefined} */
18
+ #observer;
19
+
20
+ /** @type {ResizeObserverOptions} */
21
+ #options;
22
+
23
+ /** @static */
24
+ static entries = new WeakMap();
25
+
26
+ /** @param {ResizeObserverOptions} options */
27
+ constructor(options) {
28
+ this.#options = options;
29
+ }
30
+
31
+ /**
32
+ * @param {Element} element
33
+ * @param {(entry: ResizeObserverEntry) => any} listener
34
+ */
35
+ observe(element, listener) {
36
+ var listeners = this.#listeners.get(element) || new Set();
37
+ listeners.add(listener);
38
+
39
+ this.#listeners.set(element, listeners);
40
+ this.#getObserver().observe(element, this.#options);
41
+
42
+ return () => {
43
+ var listeners = this.#listeners.get(element);
44
+ listeners.delete(listener);
45
+
46
+ if (listeners.size === 0) {
47
+ this.#listeners.delete(element);
48
+ /** @type {ResizeObserver} */ (this.#observer).unobserve(element);
49
+ }
50
+ };
51
+ }
52
+
53
+ #getObserver() {
54
+ return (
55
+ this.#observer ??
56
+ (this.#observer = new ResizeObserver(
57
+ /** @param {any} entries */ (entries) => {
58
+ for (var entry of entries) {
59
+ ResizeObserverSingleton.entries.set(entry.target, entry);
60
+ for (var listener of this.#listeners.get(entry.target) || []) {
61
+ listener(entry);
62
+ }
63
+ }
64
+ },
65
+ ))
66
+ );
67
+ }
68
+ }
69
+
70
+ var resize_observer_content_box = /* @__PURE__ */ new ResizeObserverSingleton({
71
+ box: 'content-box',
72
+ });
73
+
74
+ var resize_observer_border_box = /* @__PURE__ */ new ResizeObserverSingleton({
75
+ box: 'border-box',
76
+ });
77
+
78
+ var resize_observer_device_pixel_content_box = /* @__PURE__ */ new ResizeObserverSingleton({
79
+ box: 'device-pixel-content-box',
80
+ });
81
+
82
+ /**
83
+ * @param {string} value
84
+ */
85
+ function to_number(value) {
86
+ return value === '' ? null : +value;
87
+ }
88
+
89
+ /**
90
+ * @param {HTMLInputElement} input
91
+ */
92
+ function is_numberlike_input(input) {
93
+ var type = input.type;
94
+ return type === 'number' || type === 'range';
95
+ }
96
+
97
+ /** @param {HTMLOptionElement} option */
98
+ function get_option_value(option) {
99
+ return option.value;
100
+ }
101
+
102
+ /**
103
+ * Selects the correct option(s) (depending on whether this is a multiple select)
104
+ * @template V
105
+ * @param {HTMLSelectElement} select
106
+ * @param {V} value
107
+ * @param {boolean} mounting
108
+ */
109
+ function select_option(select, value, mounting = false) {
110
+ if (select.multiple) {
111
+ // If value is null or undefined, keep the selection as is
112
+ if (value == undefined) {
113
+ return;
114
+ }
115
+
116
+ // If not an array, warn and keep the selection as is
117
+ if (!is_array(value)) {
118
+ // TODO
119
+ }
120
+
121
+ // Otherwise, update the selection
122
+ for (var option of select.options) {
123
+ option.selected = /** @type {string[]} */ (value).includes(get_option_value(option));
124
+ }
125
+
126
+ return;
127
+ }
128
+
129
+ for (option of select.options) {
130
+ var option_value = get_option_value(option);
131
+ if (option_value === value) {
132
+ option.selected = true;
133
+ return;
134
+ }
135
+ }
136
+
137
+ if (!mounting || value !== undefined) {
138
+ select.selectedIndex = -1; // no option should be selected
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {unknown} maybe_tracked
144
+ * @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
145
+ */
146
+ export function bindValue(maybe_tracked) {
147
+ if (!is_tracked_object(maybe_tracked)) {
148
+ throw new TypeError('bindValue() argument is not a tracked object');
149
+ }
150
+
151
+ var block = /** @type {Block} */ (active_block);
152
+ var tracked = /** @type {Tracked} */ (maybe_tracked);
153
+
154
+ return (node) => {
155
+ var clear_event;
156
+
157
+ if (node.tagName === 'SELECT') {
158
+ var select = /** @type {HTMLSelectElement} */ (node);
159
+ var mounting = true;
160
+
161
+ clear_event = on(select, 'change', async () => {
162
+ var query = ':checked';
163
+ /** @type {unknown} */
164
+ var value;
165
+
166
+ if (select.multiple) {
167
+ value = [].map.call(select.querySelectorAll(query), get_option_value);
168
+ } else {
169
+ /** @type {HTMLOptionElement | null} */
170
+ var selected_option =
171
+ select.querySelector(query) ??
172
+ // will fall back to first non-disabled option if no option is selected
173
+ select.querySelector('option:not([disabled])');
174
+ value = selected_option && get_option_value(selected_option);
175
+ }
176
+
177
+ set(tracked, value, block);
178
+ });
179
+
180
+ effect(() => {
181
+ var value = get(tracked);
182
+ select_option(select, value, mounting);
183
+
184
+ // Mounting and value undefined -> take selection from dom
185
+ if (mounting && value === undefined) {
186
+ /** @type {HTMLOptionElement | null} */
187
+ var selected_option = select.querySelector(':checked');
188
+ if (selected_option !== null) {
189
+ value = get_option_value(selected_option);
190
+ set(tracked, value, block);
191
+ }
192
+ }
193
+
194
+ mounting = false;
195
+ });
196
+ } else {
197
+ var input = /** @type {HTMLInputElement} */ (node);
198
+
199
+ clear_event = on(input, 'input', async () => {
200
+ /** @type {any} */
201
+ var value = input.value;
202
+ value = is_numberlike_input(input) ? to_number(value) : value;
203
+ set(tracked, value, block);
204
+
205
+ await tick();
206
+
207
+ if (value !== (value = get(tracked))) {
208
+ var start = input.selectionStart;
209
+ var end = input.selectionEnd;
210
+ input.value = value ?? '';
211
+
212
+ // Restore selection
213
+ if (end !== null) {
214
+ input.selectionStart = start;
215
+ input.selectionEnd = Math.min(end, input.value.length);
216
+ }
217
+ }
218
+ });
219
+
220
+ render(() => {
221
+ var value = get(tracked);
222
+
223
+ if (is_numberlike_input(input) && value === to_number(input.value)) {
224
+ return;
225
+ }
226
+
227
+ if (input.type === 'date' && !value && !input.value) {
228
+ return;
229
+ }
230
+
231
+ if (value !== input.value) {
232
+ input.value = value ?? '';
233
+ }
234
+ });
235
+
236
+ return clear_event;
237
+ }
238
+ };
239
+ }
240
+
241
+ /**
242
+ * @param {unknown} maybe_tracked
243
+ * @returns {(node: HTMLInputElement) => void}
244
+ */
245
+ export function bindChecked(maybe_tracked) {
246
+ if (!is_tracked_object(maybe_tracked)) {
247
+ throw new TypeError('bindChecked() argument is not a tracked object');
248
+ }
249
+
250
+ const block = /** @type {any} */ (active_block);
251
+ const tracked = /** @type {Tracked} */ (maybe_tracked);
252
+
253
+ return (input) => {
254
+ const clear_event = on(input, 'change', () => {
255
+ set(tracked, input.checked, block);
256
+ });
257
+
258
+ return clear_event;
259
+ };
260
+ }
261
+
262
+ /**
263
+ * @param {unknown} maybe_tracked
264
+ * @param {'clientWidth' | 'clientHeight' | 'offsetWidth' | 'offsetHeight'} type
265
+ */
266
+ function bind_element_size(maybe_tracked, type) {
267
+ if (!is_tracked_object(maybe_tracked)) {
268
+ throw new TypeError(
269
+ `bind${type.charAt(0).toUpperCase() + type.slice(1)}() argument is not a tracked object`,
270
+ );
271
+ }
272
+
273
+ var block = /** @type {any} */ (active_block);
274
+ var tracked = /** @type {Tracked<any>} */ (maybe_tracked);
275
+
276
+ return (/** @type {HTMLElement} */ element) => {
277
+ var unsubscribe = resize_observer_border_box.observe(element, () =>
278
+ set(tracked, element[type], block),
279
+ );
280
+
281
+ effect(() => {
282
+ untrack(() => set(tracked, element[type], block));
283
+ return unsubscribe;
284
+ });
285
+ };
286
+ }
287
+
288
+ /**
289
+ * @param {unknown} maybe_tracked
290
+ * @returns {(node: HTMLElement) => void}
291
+ */
292
+ export function bindClientWidth(maybe_tracked) {
293
+ return bind_element_size(maybe_tracked, 'clientWidth');
294
+ }
295
+
296
+ /**
297
+ * @param {unknown} maybe_tracked
298
+ * @returns {(node: HTMLElement) => void}
299
+ */
300
+ export function bindClientHeight(maybe_tracked) {
301
+ return bind_element_size(maybe_tracked, 'clientHeight');
302
+ }
303
+
304
+ /**
305
+ * @param {unknown} maybe_tracked
306
+ * @returns {(node: HTMLElement) => void}
307
+ */
308
+ export function bindOffsetWidth(maybe_tracked) {
309
+ return bind_element_size(maybe_tracked, 'offsetWidth');
310
+ }
311
+
312
+ /**
313
+ * @param {unknown} maybe_tracked
314
+ * @returns {(node: HTMLElement) => void}
315
+ */
316
+ export function bindOffsetHeight(maybe_tracked) {
317
+ return bind_element_size(maybe_tracked, 'offsetHeight');
318
+ }
319
+
320
+ /**
321
+ * @param {unknown} maybe_tracked
322
+ * @param {'contentRect' | 'contentBoxSize' | 'borderBoxSize' | 'devicePixelContentBoxSize'} type
323
+ */
324
+ function bind_element_rect(maybe_tracked, type) {
325
+ if (!is_tracked_object(maybe_tracked)) {
326
+ throw new TypeError(
327
+ `bind${type.charAt(0).toUpperCase() + type.slice(1)}() argument is not a tracked object`,
328
+ );
329
+ }
330
+
331
+ var block = /** @type {any} */ (active_block);
332
+ var tracked = /** @type {Tracked<any>} */ (maybe_tracked);
333
+ var observer =
334
+ type === 'contentRect' || type === 'contentBoxSize'
335
+ ? resize_observer_content_box
336
+ : type === 'borderBoxSize'
337
+ ? resize_observer_border_box
338
+ : resize_observer_device_pixel_content_box;
339
+
340
+ return (/** @type {HTMLElement} */ element) => {
341
+ var unsubscribe = observer.observe(
342
+ element,
343
+ /** @param {any} entry */ (entry) => set(tracked, entry[type], block),
344
+ );
345
+
346
+ effect(() => unsubscribe);
347
+ };
348
+ }
349
+
350
+ /**
351
+ * @param {unknown} maybe_tracked
352
+ * @returns {(node: HTMLElement) => void}
353
+ */
354
+ export function bindContentRect(maybe_tracked) {
355
+ return bind_element_rect(maybe_tracked, 'contentRect');
356
+ }
357
+
358
+ /**
359
+ * @param {unknown} maybe_tracked
360
+ * @returns {(node: HTMLElement) => void}
361
+ */
362
+ export function bindContentBoxSize(maybe_tracked) {
363
+ return bind_element_rect(maybe_tracked, 'contentBoxSize');
364
+ }
365
+
366
+ /**
367
+ * @param {unknown} maybe_tracked
368
+ * @returns {(node: HTMLElement) => void}
369
+ */
370
+ export function bindBorderBoxSize(maybe_tracked) {
371
+ return bind_element_rect(maybe_tracked, 'borderBoxSize');
372
+ }
373
+
374
+ /**
375
+ * @param {unknown} maybe_tracked
376
+ * @returns {(node: HTMLElement) => void}
377
+ */
378
+ export function bindDevicePixelContentBoxSize(maybe_tracked) {
379
+ return bind_element_rect(maybe_tracked, 'devicePixelContentBoxSize');
380
+ }
381
+
382
+ /**
383
+ * @param {unknown} maybe_tracked
384
+ * @param {'innerHTML' | 'innerText' | 'textContent'} property
385
+ * @returns {(node: HTMLElement) => void}
386
+ */
387
+ export function bind_content_editable(maybe_tracked, property) {
388
+ if (!is_tracked_object(maybe_tracked)) {
389
+ throw new TypeError(
390
+ `bind${property.charAt(0).toUpperCase() + property.slice(1)}() argument is not a tracked object`,
391
+ );
392
+ }
393
+
394
+ const block = /** @type {any} */ (active_block);
395
+ const tracked = /** @type {Tracked} */ (maybe_tracked);
396
+
397
+ return (element) => {
398
+ const clear_event = on(element, 'input', () => {
399
+ set(tracked, element[property], block);
400
+ });
401
+
402
+ render(() => {
403
+ var value = get(tracked);
404
+
405
+ if (element[property] !== value) {
406
+ if (value == null) {
407
+ // @ts-ignore
408
+ var non_null_value = element[property];
409
+ set(tracked, non_null_value, block);
410
+ } else {
411
+ // @ts-ignore
412
+ element[property] = value + '';
413
+ }
414
+ }
415
+ });
416
+
417
+ return clear_event;
418
+ };
419
+ }
420
+
421
+ /**
422
+ * @param {unknown} maybe_tracked
423
+ * @returns {(node: HTMLElement) => void}
424
+ */
425
+ export function bindInnerHTML(maybe_tracked) {
426
+ return bind_content_editable(maybe_tracked, 'innerHTML');
427
+ }
428
+
429
+ /**
430
+ * @param {unknown} maybe_tracked
431
+ * @returns {(node: HTMLElement) => void}
432
+ */
433
+ export function bindInnerText(maybe_tracked) {
434
+ return bind_content_editable(maybe_tracked, 'innerText');
435
+ }
436
+
437
+ /**
438
+ * @param {unknown} maybe_tracked
439
+ * @returns {(node: HTMLElement) => void}
440
+ */
441
+ export function bindTextContent(maybe_tracked) {
442
+ return bind_content_editable(maybe_tracked, 'textContent');
443
+ }
@@ -65,6 +65,10 @@ export { tracked_array } from '../../array.js';
65
65
 
66
66
  export { tracked_object } from '../../object.js';
67
67
 
68
+ export { tracked_map } from '../../map.js';
69
+
70
+ export { tracked_set } from '../../set.js';
71
+
68
72
  export { head } from './head.js';
69
73
 
70
74
  export { script } from './script.js';
@@ -768,6 +768,16 @@ export function get_tracked(tracked) {
768
768
  return value;
769
769
  }
770
770
 
771
+ /**
772
+ * Exposed version of `set` to avoid internal bugs
773
+ * since block is required on the internal `set`
774
+ * @param {Derived | Tracked} tracked
775
+ * @param {any} value
776
+ */
777
+ export function public_set(tracked, value) {
778
+ set(tracked, value, safe_scope());
779
+ }
780
+
771
781
  /**
772
782
  * @param {Derived | Tracked} tracked
773
783
  * @param {any} value