ripple 0.2.77 → 0.2.79

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,10 +3,10 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.77",
6
+ "version": "0.2.79",
7
7
  "type": "module",
8
- "module": "src/runtime/index.js",
9
- "main": "src/runtime/index.js",
8
+ "module": "src/runtime/index-client.js",
9
+ "main": "src/runtime/index-client.js",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/trueadm/ripple.git",
@@ -24,9 +24,9 @@
24
24
  "exports": {
25
25
  ".": {
26
26
  "types": "./types/index.d.ts",
27
- "worker": "./src/runtime/index.js",
28
- "browser": "./src/runtime/index.js",
29
- "default": "./src/runtime/index.js"
27
+ "worker": "./src/runtime/index-server.js",
28
+ "browser": "./src/runtime/index-client.js",
29
+ "default": "./src/runtime/index-server.js"
30
30
  },
31
31
  "./package.json": "./package.json",
32
32
  "./server": {
@@ -478,18 +478,18 @@ const visitors = {
478
478
  continue;
479
479
  }
480
480
 
481
- if (name === 'class' || name === '$class') {
481
+ if (name === 'class') {
482
482
  class_attribute = attr;
483
483
 
484
484
  continue;
485
485
  }
486
486
 
487
- if (name === 'value' || name === '$value') {
487
+ if (name === 'value') {
488
488
  const id = state.flush_node();
489
489
  const metadata = { tracking: false, await: false };
490
490
  const expression = visit(attr.value, { ...state, metadata });
491
491
 
492
- if (name === '$value' || metadata.tracking) {
492
+ if (metadata.tracking) {
493
493
  local_updates.push(b.stmt(b.call('_$_.set_value', id, expression)));
494
494
  } else {
495
495
  state.init.push(b.stmt(b.call('_$_.set_value', id, expression)));
@@ -498,7 +498,7 @@ const visitors = {
498
498
  continue;
499
499
  }
500
500
 
501
- if (name === 'checked' || name === '$checked') {
501
+ if (name === 'checked') {
502
502
  const id = state.flush_node();
503
503
  const metadata = { tracking: false, await: false };
504
504
  const expression = visit(attr.value, { ...state, metadata });
@@ -511,12 +511,12 @@ const visitors = {
511
511
  continue;
512
512
  }
513
513
 
514
- if (name === 'selected' || name === '$selected') {
514
+ if (name === 'selected') {
515
515
  const id = state.flush_node();
516
516
  const metadata = { tracking: false, await: false };
517
517
  const expression = visit(attr.value, { ...state, metadata });
518
518
 
519
- if (name === '$selected' || metadata.tracking) {
519
+ if (metadata.tracking) {
520
520
  local_updates.push(b.stmt(b.call('_$_.set_selected', id, expression)));
521
521
  } else {
522
522
  state.init.push(b.stmt(b.call('_$_.set_selected', id, expression)));
@@ -635,11 +635,11 @@ const visitors = {
635
635
  let expression = visit(class_attribute.value, { ...state, metadata });
636
636
 
637
637
  if (node.metadata.scoped && state.component.css) {
638
- expression = b.binary('+', b.literal(state.component.css.hash + ' '), expression);
638
+ expression = b.binary('+', expression, b.literal(' ' + state.component.css.hash));
639
639
  }
640
640
  const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
641
641
 
642
- if (class_attribute.name.name === '$class' || metadata.tracking) {
642
+ if (metadata.tracking) {
643
643
  local_updates.push(
644
644
  b.stmt(b.call('_$_.set_class', id, expression, undefined, b.literal(is_html))),
645
645
  );
@@ -5,6 +5,8 @@ import path from 'node:path';
5
5
  import { print } from 'esrap';
6
6
  import {
7
7
  build_getter,
8
+ escape_html,
9
+ is_boolean_attribute,
8
10
  is_element_dom_element,
9
11
  is_inside_component,
10
12
  is_void_element,
@@ -12,6 +14,7 @@ import {
12
14
  } from '../../../utils.js';
13
15
  import is_reference from 'is-reference';
14
16
  import { escape } from '../../../../utils/escaping.js';
17
+ import { is_event_attribute } from '../../../../utils/events.js';
15
18
 
16
19
  function add_ripple_internal_import(context) {
17
20
  if (!context.state.to_ts) {
@@ -106,6 +109,8 @@ const visitors = {
106
109
  const { state, visit } = context;
107
110
 
108
111
  const is_dom_element = is_element_dom_element(node);
112
+ const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
113
+ const spread_attributes = is_spreading ? [] : null;
109
114
 
110
115
  if (is_dom_element) {
111
116
  const is_void = is_void_element(node.id.name);
@@ -115,13 +120,117 @@ const visitors = {
115
120
  );
116
121
  let class_attribute = null;
117
122
 
123
+ const handle_static_attr = (name, value) => {
124
+ const attr_value = b.literal(
125
+ ` ${name}${
126
+ is_boolean_attribute(name) && value === true
127
+ ? ''
128
+ : `="${value === true ? '' : escape_html(value, true)}"`
129
+ }`,
130
+ );
131
+
132
+ if (is_spreading) {
133
+ // For spread attributes, store just the actual value, not the full attribute string
134
+ const actual_value =
135
+ is_boolean_attribute(name) && value === true
136
+ ? b.literal(true)
137
+ : b.literal(value === true ? '' : value);
138
+ spread_attributes.push(b.prop('init', b.literal(name), actual_value));
139
+ } else {
140
+ state.init.push(
141
+ b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(String(attr_value)))),
142
+ );
143
+ }
144
+ };
145
+
118
146
  for (const attr of node.attributes) {
147
+ if (attr.type === 'Attribute') {
148
+ if (attr.name.type === 'Identifier') {
149
+ const name = attr.name.name;
150
+
151
+ if (attr.value === null) {
152
+ handle_static_attr(name, true);
153
+ continue;
154
+ }
155
+
156
+ if (attr.value.type === 'Literal' && name !== 'class') {
157
+ handle_static_attr(name, attr.value.value);
158
+ continue;
159
+ }
160
+
161
+ if (name === 'class') {
162
+ class_attribute = attr;
163
+
164
+ continue;
165
+ }
166
+
167
+ if (is_event_attribute(name)) {
168
+ continue;
169
+ }
170
+ const metadata = { tracking: false, await: false };
171
+ const expression = visit(attr.value, { ...state, metadata });
172
+
173
+ state.init.push(
174
+ b.stmt(
175
+ b.call(
176
+ b.member(b.id('__output'), b.id('push')),
177
+ b.call('_$_.attr', b.literal(name), expression),
178
+ ),
179
+ ),
180
+ );
181
+ }
182
+ } else if (attr.type === 'SpreadAttribute') {
183
+ spread_attributes.push(b.spread(visit(attr.argument, state)));
184
+ }
119
185
  }
120
186
 
121
187
  if (class_attribute !== null) {
122
- debugger;
188
+ if (class_attribute.value.type === 'Literal') {
189
+ let value = class_attribute.value.value;
190
+
191
+ if (node.metadata.scoped && state.component.css) {
192
+ value = `${state.component.css.hash} ${value}`;
193
+ }
194
+
195
+ handle_static_attr(class_attribute.name.name, value);
196
+ } else {
197
+ const metadata = { tracking: false, await: false };
198
+ let expression = visit(class_attribute.value, { ...state, metadata });
199
+
200
+ if (node.metadata.scoped && state.component.css) {
201
+ expression = b.binary('+', b.literal(state.component.css.hash + ' '), expression);
202
+ }
203
+
204
+ state.init.push(
205
+ b.stmt(
206
+ b.call(
207
+ b.member(b.id('__output'), b.id('push')),
208
+ b.call('_$_.attr', b.literal('class'), expression),
209
+ ),
210
+ ),
211
+ );
212
+ }
123
213
  } else if (node.metadata.scoped && state.component.css) {
124
- debugger;
214
+ const value = state.component.css.hash;
215
+
216
+ // TOOO
217
+ }
218
+
219
+ if (spread_attributes !== null && spread_attributes.length > 0) {
220
+ state.init.push(
221
+ b.stmt(
222
+ b.call(
223
+ b.member(b.id('__output'), b.id('push')),
224
+ b.call(
225
+ '_$_.spread_attrs',
226
+ b.object(spread_attributes),
227
+ node.metadata.scoped && state.component.css
228
+ ? b.literal(state.component.css.hash)
229
+ : undefined,
230
+ ),
231
+ ),
232
+ ),
233
+ );
125
234
  }
126
235
 
127
236
  state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`>`))));
@@ -134,8 +243,26 @@ const visitors = {
134
243
  );
135
244
  }
136
245
  } else {
137
- const props = [];
138
- state.init.push(b.stmt(b.call(node.id.name, b.call('__output.component'), b.object(props))));
246
+ const props = [];
247
+
248
+ for (const attr of node.attributes) {
249
+ if (attr.type === 'Attribute') {
250
+ if (attr.name.type === 'Identifier') {
251
+ const metadata = { tracking: false, await: false };
252
+ let property = visit(attr.value, { ...state, metadata });
253
+
254
+ props.push(b.prop('init', attr.name, property));
255
+ } else if (attr.type === 'SpreadAttribute') {
256
+ props.push(
257
+ b.spread(
258
+ visit(attr.argument, { ...state, metadata: { ...state.metadata, spread: true } }),
259
+ ),
260
+ );
261
+ }
262
+ }
263
+ }
264
+
265
+ state.init.push(b.stmt(b.call(node.id.name, b.id('__output'), b.object(props))));
139
266
  }
140
267
  },
141
268
 
@@ -146,17 +273,18 @@ const visitors = {
146
273
  }
147
274
  const body_scope = context.state.scopes.get(node.body);
148
275
 
276
+ const body = transform_body(node.body.body, {
277
+ ...context,
278
+ state: { ...context.state, scope: body_scope },
279
+ });
280
+
281
+ if (node.index) {
282
+ context.state.init.push(b.var(node.index, b.literal(0)));
283
+ body.push(b.update('++', node.index));
284
+ }
285
+
149
286
  context.state.init.push(
150
- b.for_of(
151
- context.visit(node.left),
152
- context.visit(node.right),
153
- b.block(
154
- transform_body(node.body.body, {
155
- ...context,
156
- state: { ...context.state, scope: body_scope },
157
- }),
158
- ),
159
- ),
287
+ b.for_of(context.visit(node.left), context.visit(node.right), b.block(body)),
160
288
  );
161
289
  },
162
290
 
@@ -184,7 +312,7 @@ const visitors = {
184
312
  const parent = /** @type {Node} */ (context.path.at(-1));
185
313
 
186
314
  if (is_reference(node, parent) && node.tracked) {
187
- add_ripple_internal_import(context);
315
+ add_ripple_internal_import(context);
188
316
  return b.call('_$_.get', build_getter(node, context));
189
317
  }
190
318
  },
@@ -0,0 +1,32 @@
1
+ import { DERIVED, TRACKED, UNINITIALIZED } from './internal/client/constants';
2
+ import { is_tracked_object } from './internal/client/utils';
3
+
4
+ export { create_context as createContext } from './internal/server/context.js';
5
+
6
+ export function effect() {
7
+ // NO-OP
8
+ }
9
+
10
+ export const TrackedArray = Array;
11
+
12
+ export function track(v, o) {
13
+ var is_tracked = is_tracked_object(v);
14
+
15
+ if (is_tracked) {
16
+ return v;
17
+ }
18
+
19
+ if (typeof v === 'function') {
20
+ return {
21
+ f: TRACKED | DERIVED,
22
+ fn: v,
23
+ v: UNINITIALIZED,
24
+ };
25
+ }
26
+
27
+ return {
28
+ f: TRACKED,
29
+ v,
30
+ };
31
+ }
32
+
@@ -0,0 +1,67 @@
1
+ import { active_component } from './index.js';
2
+
3
+ /**
4
+ * @template T
5
+ */
6
+ export class Context {
7
+ /**
8
+ * @param {T} initial_value
9
+ */
10
+ constructor(initial_value) {
11
+ /** @type {T} */
12
+ this._v = initial_value;
13
+ }
14
+
15
+ get() {
16
+ const component = active_component;
17
+ const context = this;
18
+
19
+ if (component === null) {
20
+ throw new Error('No active component found, cannot get context');
21
+ }
22
+ /** @type {Component | null} */
23
+ let current_component = component;
24
+
25
+ while (current_component !== null) {
26
+ const context_map = current_component.c;
27
+
28
+ if (context_map?.has(context)) {
29
+ return context_map.get(context);
30
+ }
31
+
32
+ current_component = current_component.p;
33
+ }
34
+
35
+ return context._v;
36
+ }
37
+
38
+ /**
39
+ * @template T
40
+ * @param {T} value
41
+ */
42
+ set(value) {
43
+ const component = active_component;
44
+ const context = this;
45
+
46
+ if (component === null) {
47
+ throw new Error('No active component found, cannot set context');
48
+ }
49
+
50
+ let current_context = component.c;
51
+
52
+ if (current_context === null) {
53
+ current_context = component.c = new Map();
54
+ }
55
+
56
+ current_context.set(context, value);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @template T
62
+ * @param {T} initial_value
63
+ * @returns {Context<T>}
64
+ */
65
+ export function create_context(initial_value) {
66
+ return new Context(initial_value);
67
+ }
@@ -1,7 +1,27 @@
1
- import { DERIVED, UNINITIALIZED } from "../client/constants";
2
- import { is_tracked_object } from "../client/utils";
1
+ /** @import { Derived } from '#client' */
3
2
 
4
- export { escape } from '../../../utils/escaping.js';
3
+ import { DERIVED, UNINITIALIZED } from '../client/constants';
4
+ import { is_tracked_object } from '../client/utils';
5
+
6
+ import { escape } from '../../../utils/escaping.js';
7
+ import { is_boolean_attribute } from '../../../compiler/utils';
8
+
9
+ export { escape };
10
+
11
+ export let active_component = null;
12
+
13
+ /**
14
+ * `<div translate={false}>` should be rendered as `<div translate="no">` and _not_
15
+ * `<div translate="false">`, which is equivalent to `<div translate="yes">`. There
16
+ * may be other odd cases that need to be added to this list in future
17
+ * @type {Record<string, Map<any, string>>}
18
+ */
19
+ const replacements = {
20
+ translate: new Map([
21
+ [true, 'yes'],
22
+ [false, 'no'],
23
+ ]),
24
+ };
5
25
 
6
26
  class Output {
7
27
  head = '';
@@ -12,16 +32,12 @@ class Output {
12
32
  this.#parent = parent;
13
33
  }
14
34
 
15
- component() {
16
- return new Output(this);
17
- }
18
-
19
35
  push(str) {
20
36
  this.body += str;
21
37
  }
22
38
  }
23
39
 
24
- export async function renderToString(component) {
40
+ export async function render(component) {
25
41
  const output = new Output(null);
26
42
 
27
43
  // TODO add expando "async" property to component functions during SSR
@@ -37,11 +53,16 @@ export async function renderToString(component) {
37
53
  }
38
54
 
39
55
  export function push_component() {
40
- // TODO
56
+ var component = {
57
+ c: null,
58
+ p: active_component,
59
+ };
60
+ active_component = component;
41
61
  }
42
62
 
43
63
  export function pop_component() {
44
- // TODO
64
+ var component = active_component;
65
+ active_component = component.p;
45
66
  }
46
67
 
47
68
  export async function async(fn) {
@@ -49,22 +70,56 @@ export async function async(fn) {
49
70
  }
50
71
 
51
72
  function get_derived(tracked) {
52
- let v = tracked.v;
73
+ let v = tracked.v;
53
74
 
54
- if (v === UNINITIALIZED) {
55
- v = tracked.fn();
56
- tracked.v = v;
57
- }
58
- return v;
75
+ if (v === UNINITIALIZED) {
76
+ v = tracked.fn();
77
+ tracked.v = v;
78
+ }
79
+ return v;
59
80
  }
60
81
 
61
82
  export function get(tracked) {
62
- // reflect back the value if it's not boxed
63
- if (!is_tracked_object(tracked)) {
64
- return tracked;
65
- }
66
-
67
- return (tracked.f & DERIVED) !== 0
68
- ? get_derived(/** @type {Derived} */ (tracked))
69
- : tracked.v;
83
+ // reflect back the value if it's not boxed
84
+ if (!is_tracked_object(tracked)) {
85
+ return tracked;
86
+ }
87
+
88
+ return (tracked.f & DERIVED) !== 0 ? get_derived(/** @type {Derived} */ (tracked)) : tracked.v;
89
+ }
90
+
91
+ /**
92
+ * @template V
93
+ * @param {string} name
94
+ * @param {V} value
95
+ * @param {boolean} [is_boolean]
96
+ * @returns {string}
97
+ */
98
+ export function attr(name, value, is_boolean = false) {
99
+ if (name === 'hidden' && value !== 'until-found') {
100
+ is_boolean = true;
101
+ }
102
+ if (value == null || (!value && is_boolean)) return '';
103
+ const normalized = (name in replacements && replacements[name].get(value)) || value;
104
+ const assignment = is_boolean ? '' : `="${escape(normalized, true)}"`;
105
+ return ` ${name}${assignment}`;
106
+ }
107
+
108
+ export function spread_attrs(attrs, css_hash) {
109
+ let attr_str = '';
110
+ let name;
111
+
112
+ for (name in attrs) {
113
+ var value = attrs[name];
114
+
115
+ if (typeof value === 'function') continue;
116
+
117
+ if (name === 'class' && css_hash) {
118
+ value = (value == null ? '' : value) + ' ' + css_hash;
119
+ }
120
+
121
+ attr_str += attr(name, value, is_boolean_attribute(name));
122
+ }
123
+
124
+ return attr_str;
70
125
  }
@@ -1 +1 @@
1
- export { renderToString } from '../runtime/internal/server/index.js';
1
+ export { render } from '../runtime/internal/server/index.js';
File without changes