-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(controller): add reactive props
- Loading branch information
1 parent
bc4b77a
commit 75c9b19
Showing
12 changed files
with
253 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,2 @@ | ||
// todo draw that somewhere else (a prop utils ? or part of the framework) | ||
export const reactiveProps = (props) => (comp) => | ||
function* ({ $host, ...rest }) { | ||
let pendingUpdate = false; | ||
const properties = {}; | ||
const { render } = $host; | ||
|
||
$host.render = (update = {}) => | ||
render({ | ||
...properties, | ||
...update, | ||
}); | ||
|
||
Object.defineProperties( | ||
$host, | ||
Object.fromEntries( | ||
props.map((propName) => { | ||
properties[propName] = $host[propName]; | ||
return [ | ||
propName, | ||
{ | ||
enumerable: true, | ||
get() { | ||
return properties[propName]; | ||
}, | ||
set(value) { | ||
properties[propName] = value; | ||
pendingUpdate = true; | ||
window.queueMicrotask(() => { | ||
pendingUpdate = false; | ||
$host.render(); | ||
}); | ||
}, | ||
}, | ||
]; | ||
}), | ||
), | ||
); | ||
|
||
yield* comp({ $host, ...rest }); | ||
}; | ||
import { withProps } from '@cofn/controllers'; | ||
export const reactiveProps = withProps; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,77 @@ | ||
# Controllers | ||
|
||
A set of higher order function to add update logic to a coroutine component | ||
|
||
## Installation | ||
|
||
you can install the library with a package manager (like npm): | ||
``npm install @cofn/controllers`` | ||
|
||
Or import it directly from a CDN | ||
|
||
## Reactive props | ||
|
||
Defines a list of properties to watch. The namespace ``properties`` is injected into the rendering generator | ||
|
||
```js | ||
import {define} from '@cofn/core'; | ||
import {withProps} from '@cofn/controllers' | ||
|
||
const withName = withProps(['name']); | ||
|
||
define('my-comp', withNameAndAge(function *({$root}){ | ||
while(true) { | ||
const { properties } = yield; | ||
$root.textContent = properties.name; | ||
} | ||
})); | ||
|
||
// <my-comp></my-comp> | ||
|
||
myCompEl.name = 'Bob'; // > render | ||
|
||
// ... | ||
|
||
myCompEl.name = 'Woot'; // > render | ||
|
||
``` | ||
|
||
## Controller API | ||
|
||
Defines a controller passed to the rendering generator. Takes as input a factory function which returns the controller. | ||
|
||
The regular component dependencies are injected into the controller factory and a meta object ``state``. | ||
Whenever a property is set on this meta object, the component renders. The namespace ``state`` is injected into the rendering generator. | ||
|
||
```js | ||
import {define} from '@cofn/core'; | ||
import {withController} from '@cofn/controller'; | ||
|
||
const withCountController = withController(({state, $host}) => { | ||
const step = $host.hasAttribute('step') ? Number($host.getAttribute('step')) : 1; | ||
state.count = 0; | ||
|
||
return { | ||
increment(){ | ||
state = state + step; | ||
}, | ||
decrement(){ | ||
state = state - step; | ||
} | ||
}; | ||
}); | ||
|
||
define('count-me',withCountController(function *({$root, controller}){ | ||
$root.replaceChildren(template.content.cloneNode(true)); | ||
const [decrementEl, incrementEl] = $host.querySelectorAll('button'); | ||
const countEl = $host.querySelector('span'); | ||
|
||
decrementEl.addEventListener('click', controller.decrement); | ||
incrementEl.addEventListener('click', controller.increment); | ||
|
||
while(true) { | ||
const { $scope } = yield; | ||
countEl.textContent = $scope.count; | ||
} | ||
})); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './controller' | ||
export * from './controller'; | ||
export * from './props.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
export const withProps = (props) => (gen) => | ||
function* ({ $host, ...rest }) { | ||
const properties = {} || rest.properties; | ||
const { render } = $host; | ||
|
||
$host.render = (update = {}) => | ||
render({ | ||
properties: { | ||
...properties, | ||
}, | ||
...update, | ||
}); | ||
|
||
Object.defineProperties( | ||
$host, | ||
Object.fromEntries( | ||
props.map((propName) => { | ||
properties[propName] = $host[propName]; | ||
return [ | ||
propName, | ||
{ | ||
enumerable: true, | ||
get() { | ||
return properties[propName]; | ||
}, | ||
set(value) { | ||
properties[propName] = value; | ||
$host.render(); | ||
}, | ||
}, | ||
]; | ||
}), | ||
), | ||
); | ||
|
||
yield* gen({ $host, ...rest }); | ||
}; |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { test } from '@cofn/test-lib/client'; | ||
import { withProps } from '../src/index.js'; | ||
import { define } from '@cofn/core'; | ||
import { nextTick } from './utils.js'; | ||
|
||
const debug = document.getElementById('debug'); | ||
const withTestProps = withProps(['test', 'other']); | ||
|
||
define( | ||
'test-props-controller', | ||
withTestProps(function* ({ $host }) { | ||
let loopCount = 0; | ||
Object.defineProperty($host, 'count', { | ||
get() { | ||
return loopCount; | ||
}, | ||
}); | ||
try { | ||
while (true) { | ||
const { properties } = yield; | ||
loopCount += 1; | ||
$host.textContent = JSON.stringify(properties); | ||
} | ||
} finally { | ||
$host.teardown = true; | ||
} | ||
}), | ||
); | ||
|
||
const withEl = (specFn) => | ||
async function zora_spec_fn(assert) { | ||
const el = document.createElement('test-props-controller'); | ||
debug.appendChild(el); | ||
try { | ||
await specFn({ ...assert, el }); | ||
} catch (err) { | ||
console.log(err); | ||
throw err; | ||
} | ||
}; | ||
|
||
test( | ||
'component is rendered with initial set properties', | ||
withEl(async ({ eq }) => { | ||
const el = document.createElement('test-props-controller'); | ||
el.test = 'foo'; | ||
await nextTick(); | ||
eq(el.textContent, JSON.stringify({ test: 'foo' })); | ||
}), | ||
); | ||
|
||
test( | ||
'component is updated when a property is set', | ||
withEl(async ({ eq, el }) => { | ||
el.test = 'foo'; | ||
el.other = 'blah'; | ||
await nextTick(); | ||
eq(el.textContent, JSON.stringify({ test: 'foo', other: 'blah' })); | ||
el.test = 42; | ||
await nextTick(); | ||
eq(el.textContent, JSON.stringify({ test: 42, other: 'blah' })); | ||
}), | ||
); | ||
|
||
test( | ||
'component is updated when a property is set', | ||
withEl(async ({ eq, el }) => { | ||
el.test = 'foo'; | ||
el.other = 'blah'; | ||
await nextTick(); | ||
eq(el.count, 1); | ||
eq(el.textContent, JSON.stringify({ test: 'foo', other: 'blah' })); | ||
el.test = 42; | ||
await nextTick(); | ||
eq(el.count, 2); | ||
eq(el.textContent, JSON.stringify({ test: 42, other: 'blah' })); | ||
}), | ||
); | ||
|
||
test( | ||
'component is updated once when several properties are set', | ||
withEl(async ({ eq, el }) => { | ||
el.test = 'foo'; | ||
el.other = 'blah'; | ||
await nextTick(); | ||
eq(el.count, 1); | ||
eq(el.textContent, JSON.stringify({ test: 'foo', other: 'blah' })); | ||
el.test = 42; | ||
el.other = 'updated'; | ||
await nextTick(); | ||
eq(el.count, 2); | ||
eq(el.textContent, JSON.stringify({ test: 42, other: 'updated' })); | ||
}), | ||
); | ||
|
||
test( | ||
'component is not updated when a property is set but the property is not in the reactive property list', | ||
withEl(async ({ eq, el }) => { | ||
el.test = 'foo'; | ||
el.other = 'blah'; | ||
await nextTick(); | ||
eq(el.count, 1); | ||
eq(el.textContent, JSON.stringify({ test: 'foo', other: 'blah' })); | ||
el.whatever = 42; | ||
await nextTick(); | ||
eq(el.count, 1); | ||
eq(el.textContent, JSON.stringify({ test: 'foo', other: 'blah' })); | ||
}), | ||
); | ||
|
||
test( | ||
'tears down of the component is called', | ||
withEl(async ({ ok, notOk, el }) => { | ||
notOk(el.teardown); | ||
el.remove(); | ||
await nextTick(); | ||
ok(el.teardown); | ||
}), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters