ripple 0.2.112 → 0.2.114
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/phases/1-parse/index.js +1 -0
- package/src/compiler/phases/3-transform/client/index.js +18 -13
- package/src/compiler/phases/3-transform/segments.js +1 -1
- package/src/runtime/index-client.js +185 -5
- package/src/runtime/internal/client/runtime.js +4 -4
- package/src/runtime/internal/client/types.d.ts +2 -2
- package/src/utils/builders.js +274 -262
- package/tests/client/input-value.test.ripple +123 -0
- package/types/index.d.ts +12 -0
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.114",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -204,7 +204,7 @@ const visitors = {
|
|
|
204
204
|
return {
|
|
205
205
|
...node,
|
|
206
206
|
specifiers: node.specifiers
|
|
207
|
-
.filter((spec) => spec.importKind !== 'type')
|
|
207
|
+
.filter((spec) => context.state.to_ts || spec.importKind !== 'type')
|
|
208
208
|
.map((spec) => context.visit(spec)),
|
|
209
209
|
};
|
|
210
210
|
},
|
|
@@ -1462,23 +1462,28 @@ function transform_ts_child(node, context) {
|
|
|
1462
1462
|
}
|
|
1463
1463
|
}
|
|
1464
1464
|
|
|
1465
|
-
|
|
1465
|
+
let tracked_type;
|
|
1466
|
+
|
|
1467
|
+
// Make VSCode happy about tracked components, as they're lowercase in the AST
|
|
1468
|
+
// so VSCode doesn't bother tracking them as components, so this fixes that
|
|
1469
|
+
if (node.id.tracked) {
|
|
1470
|
+
tracked_type = state.scope.generate(type[0].toUpperCase() + type.slice(1));
|
|
1471
|
+
state.init.push(
|
|
1472
|
+
b.const(tracked_type, b.member(node.id, b.literal('#v'), true)),
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const opening_type = tracked_type
|
|
1477
|
+
? b.jsx_id(tracked_type)
|
|
1478
|
+
: b.jsx_id(type);
|
|
1466
1479
|
opening_type.loc = node.id.loc;
|
|
1467
1480
|
|
|
1468
1481
|
let closing_type = undefined;
|
|
1469
1482
|
|
|
1470
1483
|
if (!node.selfClosing) {
|
|
1471
|
-
closing_type =
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
line: node.loc.end.line,
|
|
1475
|
-
column: node.loc.end.column - type.length - 1,
|
|
1476
|
-
},
|
|
1477
|
-
end: {
|
|
1478
|
-
line: node.loc.end.line,
|
|
1479
|
-
column: node.loc.end.column - 1,
|
|
1480
|
-
},
|
|
1481
|
-
};
|
|
1484
|
+
closing_type = tracked_type
|
|
1485
|
+
? b.jsx_id(tracked_type)
|
|
1486
|
+
: b.jsx_id(type);
|
|
1482
1487
|
}
|
|
1483
1488
|
|
|
1484
1489
|
state.init.push(
|
|
@@ -119,7 +119,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
119
119
|
visit(specifier);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
visit(node.source);
|
|
123
123
|
return;
|
|
124
124
|
} else if (node.type === 'ImportSpecifier') {
|
|
125
125
|
// If local and imported are the same, only visit local to avoid duplicates
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/** @import { Block,
|
|
1
|
+
/** @import { Block, Tracked } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { destroy_block, root } from './internal/client/blocks.js';
|
|
4
|
-
import { handle_root_events } from './internal/client/events.js';
|
|
3
|
+
import { destroy_block, effect, render, root } from './internal/client/blocks.js';
|
|
4
|
+
import { handle_root_events, on } from './internal/client/events.js';
|
|
5
5
|
import { init_operations } from './internal/client/operations.js';
|
|
6
|
-
import { active_block,
|
|
7
|
-
import { create_anchor } from './internal/client/utils.js';
|
|
6
|
+
import { active_block, get, set, tick } from './internal/client/runtime.js';
|
|
7
|
+
import { create_anchor, is_array, is_tracked_object } from './internal/client/utils.js';
|
|
8
8
|
import { remove_ssr_css } from './internal/client/css.js';
|
|
9
9
|
|
|
10
10
|
// Re-export JSX runtime functions for jsxImportSource: "ripple"
|
|
@@ -77,3 +77,183 @@ export { Portal } from './internal/client/portal.js';
|
|
|
77
77
|
export { ref_prop as createRefKey } from './internal/client/runtime.js';
|
|
78
78
|
|
|
79
79
|
export { on } from './internal/client/events.js';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} value
|
|
83
|
+
*/
|
|
84
|
+
function to_number(value) {
|
|
85
|
+
return value === '' ? null : +value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {HTMLInputElement} input
|
|
90
|
+
*/
|
|
91
|
+
function is_numberlike_input(input) {
|
|
92
|
+
var type = input.type;
|
|
93
|
+
return type === 'number' || type === 'range';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @param {HTMLOptionElement} option */
|
|
97
|
+
function get_option_value(option) {
|
|
98
|
+
return option.value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Selects the correct option(s) (depending on whether this is a multiple select)
|
|
103
|
+
* @template V
|
|
104
|
+
* @param {HTMLSelectElement} select
|
|
105
|
+
* @param {V} value
|
|
106
|
+
* @param {boolean} mounting
|
|
107
|
+
*/
|
|
108
|
+
function select_option(select, value, mounting = false) {
|
|
109
|
+
if (select.multiple) {
|
|
110
|
+
// If value is null or undefined, keep the selection as is
|
|
111
|
+
if (value == undefined) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// If not an array, warn and keep the selection as is
|
|
116
|
+
if (!is_array(value)) {
|
|
117
|
+
// TODO
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Otherwise, update the selection
|
|
121
|
+
for (var option of select.options) {
|
|
122
|
+
option.selected = /** @type {string[]} */ (value).includes(get_option_value(option));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (option of select.options) {
|
|
129
|
+
var option_value = get_option_value(option);
|
|
130
|
+
if (option_value === value) {
|
|
131
|
+
option.selected = true;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!mounting || value !== undefined) {
|
|
137
|
+
select.selectedIndex = -1; // no option should be selected
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {unknown} maybe_tracked
|
|
143
|
+
* @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
|
|
144
|
+
*/
|
|
145
|
+
export function bindValue(maybe_tracked) {
|
|
146
|
+
if (!is_tracked_object(maybe_tracked)) {
|
|
147
|
+
throw new TypeError('bindValue() argument is not a tracked object');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var block = /** @type {Block} */ (active_block);
|
|
151
|
+
var tracked = /** @type {Tracked} */ (maybe_tracked);
|
|
152
|
+
|
|
153
|
+
return (node) => {
|
|
154
|
+
var clear_event;
|
|
155
|
+
|
|
156
|
+
if (node.tagName === 'SELECT') {
|
|
157
|
+
var select = /** @type {HTMLSelectElement} */ (node);
|
|
158
|
+
var mounting = true;
|
|
159
|
+
|
|
160
|
+
clear_event = on(select, 'change', async () => {
|
|
161
|
+
var query = ':checked';
|
|
162
|
+
/** @type {unknown} */
|
|
163
|
+
var value;
|
|
164
|
+
|
|
165
|
+
if (select.multiple) {
|
|
166
|
+
value = [].map.call(select.querySelectorAll(query), get_option_value);
|
|
167
|
+
} else {
|
|
168
|
+
/** @type {HTMLOptionElement | null} */
|
|
169
|
+
var selected_option =
|
|
170
|
+
select.querySelector(query) ??
|
|
171
|
+
// will fall back to first non-disabled option if no option is selected
|
|
172
|
+
select.querySelector('option:not([disabled])');
|
|
173
|
+
value = selected_option && get_option_value(selected_option);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
set(tracked, value, block);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
effect(() => {
|
|
180
|
+
var value = get(tracked);
|
|
181
|
+
select_option(select, value, mounting);
|
|
182
|
+
|
|
183
|
+
// Mounting and value undefined -> take selection from dom
|
|
184
|
+
if (mounting && value === undefined) {
|
|
185
|
+
/** @type {HTMLOptionElement | null} */
|
|
186
|
+
var selected_option = select.querySelector(':checked');
|
|
187
|
+
if (selected_option !== null) {
|
|
188
|
+
value = get_option_value(selected_option);
|
|
189
|
+
set(tracked, value, block);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
mounting = false;
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
var input = /** @type {HTMLInputElement} */ (node);
|
|
197
|
+
|
|
198
|
+
clear_event = on(input, 'input', async () => {
|
|
199
|
+
/** @type {any} */
|
|
200
|
+
var value = input.value;
|
|
201
|
+
value = is_numberlike_input(input) ? to_number(value) : value;
|
|
202
|
+
set(tracked, value, block);
|
|
203
|
+
|
|
204
|
+
await tick();
|
|
205
|
+
|
|
206
|
+
if (value !== (value = get(tracked))) {
|
|
207
|
+
var start = input.selectionStart;
|
|
208
|
+
var end = input.selectionEnd;
|
|
209
|
+
input.value = value ?? '';
|
|
210
|
+
|
|
211
|
+
// Restore selection
|
|
212
|
+
if (end !== null) {
|
|
213
|
+
input.selectionStart = start;
|
|
214
|
+
input.selectionEnd = Math.min(end, input.value.length);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
render(() => {
|
|
220
|
+
var value = get(tracked);
|
|
221
|
+
|
|
222
|
+
if (is_numberlike_input(input) && value === to_number(input.value)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (input.type === 'date' && !value && !input.value) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (value !== input.value) {
|
|
231
|
+
input.value = value ?? '';
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return clear_event;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {unknown} maybe_tracked
|
|
242
|
+
* @returns {(node: HTMLInputElement) => void}
|
|
243
|
+
*/
|
|
244
|
+
export function bindChecked(maybe_tracked) {
|
|
245
|
+
if (!is_tracked_object(maybe_tracked)) {
|
|
246
|
+
throw new TypeError('bindChecked() argument is not a tracked object');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const block = /** @type {any} */ (active_block);
|
|
250
|
+
const tracked = /** @type {Tracked<any>} */ (maybe_tracked);
|
|
251
|
+
|
|
252
|
+
return (input) => {
|
|
253
|
+
const clear_event = on(input, 'change', () => {
|
|
254
|
+
set(tracked, input.checked, block);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return clear_event;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -269,16 +269,16 @@ var empty_get_set = { get: undefined, set: undefined };
|
|
|
269
269
|
/**
|
|
270
270
|
*
|
|
271
271
|
* @param {any} v
|
|
272
|
-
* @param {Block}
|
|
272
|
+
* @param {Block} block
|
|
273
273
|
* @param {(value: any) => any} [get]
|
|
274
274
|
* @param {(next: any, prev: any) => any} [set]
|
|
275
275
|
* @returns {Tracked}
|
|
276
276
|
*/
|
|
277
|
-
export function tracked(v,
|
|
277
|
+
export function tracked(v, block, get, set) {
|
|
278
278
|
// TODO: now we expose tracked, we should likely block access in DEV somehow
|
|
279
279
|
return {
|
|
280
280
|
a: get || set ? { get, set } : empty_get_set,
|
|
281
|
-
b,
|
|
281
|
+
b: block || active_block,
|
|
282
282
|
c: 0,
|
|
283
283
|
f: TRACKED,
|
|
284
284
|
v,
|
|
@@ -295,7 +295,7 @@ export function tracked(v, b, get, set) {
|
|
|
295
295
|
export function derived(fn, block, get, set) {
|
|
296
296
|
return {
|
|
297
297
|
a: get || set ? { get, set } : empty_get_set,
|
|
298
|
-
b: block,
|
|
298
|
+
b: block || active_block,
|
|
299
299
|
blocks: null,
|
|
300
300
|
c: 0,
|
|
301
301
|
co: active_component,
|
|
@@ -17,12 +17,12 @@ export type Dependency = {
|
|
|
17
17
|
n: null | Dependency;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export type Tracked = {
|
|
20
|
+
export type Tracked<V = any> = {
|
|
21
21
|
a: { get?: Function, set?: Function };
|
|
22
22
|
b: Block;
|
|
23
23
|
c: number;
|
|
24
24
|
f: number;
|
|
25
|
-
v:
|
|
25
|
+
v: V;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export type Derived = {
|