ripple 0.2.7 → 0.2.9

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,7 +3,7 @@
3
3
  "description": "Ripple is a TypeScript UI framework for the web",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.7",
6
+ "version": "0.2.9",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -67,7 +67,6 @@ function RipplePlugin(config) {
67
67
  }
68
68
 
69
69
  jsx_parseExpressionContainer() {
70
- const tok = this.acornTypeScript.tokTypes;
71
70
  let node = this.startNode();
72
71
  this.next();
73
72
 
@@ -136,13 +135,6 @@ function RipplePlugin(config) {
136
135
  this.raise(t.start, 'attributes must only be assigned a non-empty expression'),
137
136
  t
138
137
  );
139
- // case tt.bracketL:
140
- // var t = this.jsx_parseTupleContainer();
141
- // return (
142
- // 'JSXEmptyExpression' === t.expression.type &&
143
- // this.raise(t.start, 'attributes must only be assigned a non-empty expression'),
144
- // t
145
- // );
146
138
  case tok.jsxTagStart:
147
139
  case tt.string:
148
140
  return this.parseExprAtom();
@@ -294,7 +286,10 @@ function RipplePlugin(config) {
294
286
  case 62: // '>'
295
287
  case 125: {
296
288
  // '}'
297
- if (ch === 125 && (this.#path.length === 0 || this.#path.at(-1)?.type === 'Component')) {
289
+ if (
290
+ ch === 125 &&
291
+ (this.#path.length === 0 || this.#path.at(-1)?.type === 'Component')
292
+ ) {
298
293
  return original.readToken.call(this, ch);
299
294
  }
300
295
  this.raise(
@@ -364,12 +359,8 @@ function RipplePlugin(config) {
364
359
 
365
360
  if (element.selfClosing) {
366
361
  this.#path.pop();
367
- if (
368
- this.type !== tok.jsxTagStart &&
369
- this.type?.keyword !== 'for' &&
370
- this.type?.keyword !== 'try'
371
- ) {
372
- // Eat the closing `/>`
362
+
363
+ if (this.type.label === '</>/<=/>=') {
373
364
  this.pos--;
374
365
  this.next();
375
366
  }
@@ -400,6 +391,13 @@ function RipplePlugin(config) {
400
391
  } else {
401
392
  this.parseTemplateBody(element.children);
402
393
  }
394
+ const tokContexts = this.acornTypeScript.tokContexts;
395
+
396
+ const curContext = this.curContext();
397
+
398
+ if (curContext === tokContexts.tc_expr) {
399
+ this.context.pop();
400
+ }
403
401
  }
404
402
 
405
403
  this.finishNode(element, 'Element');
@@ -806,10 +806,12 @@ const visitors = {
806
806
  const binding = context.state.scope.get(left.name);
807
807
  const transformers = left && binding?.transform;
808
808
 
809
- if (left === argument && transformers?.update) {
810
- // we don't need to worry about ownership_invalid_mutation here, because
811
- // we're not mutating but reassigning
812
- return transformers.update(node);
809
+ if (left === argument ) {
810
+ if (transformers?.update) {
811
+ return transformers.update(node);
812
+ } else if (binding.kind === 'prop') {
813
+ throw new Error('Cannot update component prop property, component props are not writable');
814
+ }
813
815
  }
814
816
 
815
817
  context.next();
@@ -37,3 +37,5 @@ export { keyed } from './internal/client/for.js';
37
37
  export { user_effect as effect } from './internal/client/blocks.js';
38
38
 
39
39
  export { Portal } from './internal/client/portal.js';
40
+
41
+ export { ref } from './internal/client/runtime.js';
@@ -19,6 +19,7 @@ import {
19
19
  run_block,
20
20
  run_teardown,
21
21
  schedule_update,
22
+ set_property,
22
23
  } from './runtime';
23
24
  import { suspend } from './try';
24
25
 
@@ -69,19 +70,29 @@ export function async(fn) {
69
70
  }
70
71
 
71
72
  export function use(element, get_fn) {
72
- var fn = undefined;
73
+ var use_obj = undefined;
73
74
  var e;
75
+ var current_block = active_block;
74
76
 
75
77
  return block(RENDER_BLOCK, () => {
76
- if (fn !== (fn = get_fn())) {
78
+ if (use_obj !== (use_obj = get_fn())) {
77
79
  if (e) {
78
80
  destroy_block(e);
79
81
  e = null;
80
82
  }
81
83
 
82
- if (fn) {
84
+ if (use_obj) {
83
85
  e = branch(() => {
84
- effect(() => fn(element));
86
+ effect(() => {
87
+ if (typeof use_obj === 'function') {
88
+ return use_obj(element);
89
+ } else {
90
+ set_property(use_obj, '$current', element, current_block);
91
+ return () => {
92
+ set_property(use_obj, '$current', null, current_block);
93
+ };
94
+ }
95
+ });
85
96
  });
86
97
  }
87
98
  }
@@ -46,8 +46,7 @@ export function for_block(node, get_collection, render_fn, flags) {
46
46
  : array_from(collection);
47
47
 
48
48
  if (array[TRACKED_OBJECT] !== undefined) {
49
- // TODO we previously assigned array to this, but why?
50
- get_all_elements(collection);
49
+ array = get_all_elements(collection);
51
50
  collection.$length;
52
51
  }
53
52
 
@@ -936,3 +936,16 @@ export function pop_component() {
936
936
  export function use_prop() {
937
937
  return Symbol(USE_PROP);
938
938
  }
939
+
940
+ export function ref(value) {
941
+ var block = active_block || active_scope;
942
+ if (!block) {
943
+ throw new Error('ref() must be called within a component or reactive context');
944
+ }
945
+
946
+ // Create a tracked object with a $current property
947
+ var ref_obj = { $current: value };
948
+
949
+ // Make the $current property reactive
950
+ return tracked_object(ref_obj, ['$current'], block);
951
+ }
@@ -0,0 +1,81 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`for statements > render a simple dynamic array 1`] = `
4
+ <div>
5
+ <!---->
6
+ <div
7
+ class="Item 1"
8
+ >
9
+ Item 1
10
+ </div>
11
+ <div
12
+ class="Item 2"
13
+ >
14
+ Item 2
15
+ </div>
16
+ <div
17
+ class="Item 3"
18
+ >
19
+ Item 3
20
+ </div>
21
+ <!---->
22
+ <button>
23
+ Add Item
24
+ </button>
25
+
26
+ </div>
27
+ `;
28
+
29
+ exports[`for statements > render a simple dynamic array 2`] = `
30
+ <div>
31
+ <!---->
32
+ <div
33
+ class="Item 1"
34
+ >
35
+ Item 1
36
+ </div>
37
+ <div
38
+ class="Item 2"
39
+ >
40
+ Item 2
41
+ </div>
42
+ <div
43
+ class="Item 3"
44
+ >
45
+ Item 3
46
+ </div>
47
+ <div
48
+ class="Item 4"
49
+ >
50
+ Item 4
51
+ </div>
52
+ <!---->
53
+ <button>
54
+ Add Item
55
+ </button>
56
+
57
+ </div>
58
+ `;
59
+
60
+ exports[`for statements > render a simple static array 1`] = `
61
+ <div>
62
+ <!---->
63
+ <div
64
+ class="Item 1"
65
+ >
66
+ Item 1
67
+ </div>
68
+ <div
69
+ class="Item 2"
70
+ >
71
+ Item 2
72
+ </div>
73
+ <div
74
+ class="Item 3"
75
+ >
76
+ Item 3
77
+ </div>
78
+ <!---->
79
+
80
+ </div>
81
+ `;
@@ -21,6 +21,32 @@ describe('composite components', () => {
21
21
  container = null;
22
22
  });
23
23
 
24
+ it('renders composite components', () => {
25
+ component Button({ $count }) {
26
+ <div>{$count}</div>
27
+ }
28
+
29
+ component App() {
30
+ let $count = 0;
31
+ <button onClick={() => $count++}>{'Increment'}</button>
32
+ <Button $count={$count} />
33
+ }
34
+
35
+ render(App);
36
+
37
+ const button = container.querySelector('button');
38
+
39
+ button.click();
40
+ flushSync();
41
+
42
+ expect(container.querySelector('div').textContent).toBe('1');
43
+
44
+ button.click();
45
+ flushSync();
46
+
47
+ expect(container.querySelector('div').textContent).toBe('2');
48
+ });
49
+
24
50
  it('renders composite components with object state', () => {
25
51
  component Button({ obj }) {
26
52
  <button class='count2' onClick={() => {
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+
3
+ import { mount, flushSync, array } from 'ripple';
4
+
5
+ describe('for statements', () => {
6
+ let container;
7
+
8
+ function render(component) {
9
+ mount(component, {
10
+ target: container
11
+ });
12
+ }
13
+
14
+ beforeEach(() => {
15
+ container = document.createElement('div');
16
+ document.body.appendChild(container);
17
+ });
18
+
19
+ afterEach(() => {
20
+ document.body.removeChild(container);
21
+ container = null;
22
+ });
23
+
24
+ it('render a simple static array', () => {
25
+ component App() {
26
+ const items = ['Item 1', 'Item 2', 'Item 3'];
27
+
28
+ for (const item of items) {
29
+ <div class={item}>{item}</div>
30
+ }
31
+ }
32
+
33
+ render(App);
34
+
35
+ expect(container).toMatchSnapshot();
36
+ });
37
+
38
+ it('render a simple dynamic array', () => {
39
+ component App() {
40
+ const items = array('Item 1', 'Item 2', 'Item 3');
41
+
42
+ for (const item of items) {
43
+ <div class={item}>{item}</div>
44
+ }
45
+
46
+ <button onClick={() => items.push(`Item ${items.$length + 1}`)}>{"Add Item"}</button>
47
+ }
48
+
49
+ render(App);
50
+ expect(container).toMatchSnapshot();
51
+
52
+ const button = container.querySelector('button');
53
+ button.click();
54
+ flushSync();
55
+
56
+ expect(container).toMatchSnapshot();
57
+ });
58
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+
3
+ import { mount, flushSync, ref } from 'ripple';
4
+
5
+ describe('ref', () => {
6
+ let container;
7
+
8
+ function render(component) {
9
+ mount(component, {
10
+ target: container
11
+ });
12
+ }
13
+
14
+ beforeEach(() => {
15
+ container = document.createElement('div');
16
+ document.body.appendChild(container);
17
+ });
18
+
19
+ afterEach(() => {
20
+ document.body.removeChild(container);
21
+ });
22
+
23
+ it('creates a reactive ref with initial value', () => {
24
+ component TestRef() {
25
+ let $count = 5;
26
+ <div><span id='count'>{$count}</span></div>
27
+ }
28
+
29
+ render(TestRef);
30
+
31
+ expect(container.querySelector('#count').textContent).toBe('5');
32
+ });
33
+
34
+ it('updates when ref value changes', () => {
35
+ component TestRef() {
36
+ let $count = 0;
37
+ <div>
38
+ <span id='count'>{$count}</span>
39
+ <button id='btn' onClick={() => $count++}>{'Increment'}</button>
40
+ </div>
41
+ }
42
+
43
+ render(TestRef);
44
+
45
+ expect(container.querySelector('#count').textContent).toBe('0');
46
+
47
+ container.querySelector('#btn').click();
48
+ flushSync();
49
+
50
+ expect(container.querySelector('#count').textContent).toBe('1');
51
+ });
52
+ });
package/types/index.d.ts CHANGED
@@ -10,3 +10,9 @@ export declare function untrack<T>(fn: () => T): T;
10
10
  export declare function flushSync<T>(fn: () => T): T;
11
11
 
12
12
  export declare function effect(fn: (() => void) | (() => () => void)): void;
13
+
14
+ export interface Ref<T> {
15
+ $current: T;
16
+ }
17
+
18
+ export declare function ref<T>(value: T): Ref<T>;