-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #952 from NullVoxPopuli/modifiers
Modifiers
- Loading branch information
Showing
12 changed files
with
1,893 additions
and
635 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
"ember-resources": minor | ||
--- | ||
|
||
Introduce resources as modifiers. | ||
This brings alignment with Starbeam's plans for modifiers as a universal primitive. | ||
|
||
In ember-resources, using modifiers as resources looks like this: | ||
|
||
```js | ||
import { resource } from 'ember-resources'; | ||
import { modifier } from 'ember-resources/modifier'; | ||
|
||
const wiggle = modifier((element, arg1, arg2, namedArgs) => { | ||
return resource(({ on }) => { | ||
let animation = element.animate([ | ||
{ transform: `translateX(${arg1}px)` }, | ||
{ transform: `translateX(-${arg2}px)` }, | ||
], { | ||
duration: 100, | ||
iterations: Infinity, | ||
}); | ||
|
||
on.cleanup(() => animation.cancel()); | ||
}); | ||
}); | ||
|
||
<template> | ||
<div {{wiggle 2 5 named="hello"}}>hello</div> | ||
</template> | ||
``` | ||
|
||
The signature for the modifier here is _different_ from `ember-modifier`, where positional args and named args are grouped together into an array and object respectively. | ||
|
||
This signature for ember-resource's `modifier` follows the [plain function invocation](https://blog.emberjs.com/plain-old-functions-as-helpers/) signature. | ||
|
||
<details><summary>in Starbeam</summary> | ||
|
||
```js | ||
import { resource } from '@starbeam/universal'; | ||
|
||
function wiggle(element, arg1, arg2, namedArgs) { | ||
return resource(({ on }) => { | ||
let animation = element.animate([ | ||
{ transform: `translateX(${arg1}px)` }, | ||
{ transform: `translateX(-${arg2}px)` }, | ||
], { | ||
duration: 100, | ||
iterations: Infinity, | ||
}); | ||
|
||
on.cleanup(() => animation.cancel()); | ||
}); | ||
} | ||
|
||
<template> | ||
<div {{wiggle 2 5 named="hello"}}>hello</div> | ||
</template> | ||
``` | ||
|
||
</details> |
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
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,227 @@ | ||
import { assert } from '@ember/debug'; | ||
// @ts-expect-error | ||
import { setModifierManager } from '@ember/modifier'; | ||
|
||
import { resourceFactory } from '../index'; | ||
import FunctionBasedModifierManager from './manager'; | ||
|
||
import type { resource } from '../index'; | ||
import type { ArgsFor, ElementFor, EmptyObject } from '[core-types]'; | ||
import type { ModifierLike } from '@glint/template'; | ||
|
||
// Provide a singleton manager. | ||
const MANAGER = new FunctionBasedModifierManager(); | ||
|
||
type PositionalArgs<S> = S extends { Args?: object } ? ArgsFor<S['Args']>['Positional'] : []; | ||
type NamedArgs<S> = S extends { Args?: object } | ||
? ArgsFor<S['Args']>['Named'] extends object | ||
? ArgsFor<S['Args']>['Named'] | ||
: EmptyObject | ||
: EmptyObject; | ||
|
||
type ArgsForFn<S> = S extends { Args?: object } | ||
? ArgsFor<S['Args']>['Named'] extends EmptyObject | ||
? [...PositionalArgs<S>] | ||
: [...PositionalArgs<S>, NamedArgs<S>] | ||
: []; | ||
|
||
/** | ||
* A resource-based API for building modifiers. | ||
* | ||
* You can attach this to an element, and use a `resource` to manage | ||
* the state, add event listeners, remove event listeners on cleanup, etc. | ||
* | ||
* Using resources for modifiers provides a clear and concise API with | ||
* easy to read concerns. | ||
* | ||
* | ||
* The signature for the modifier here is _different_ from `ember-modifier`, where positional args and named args are grouped together into an array and object respectively. | ||
* This signature for ember-resource's `modifier` follows the [plain function invocation](https://blog.emberjs.com/plain-old-functions-as-helpers/) signature. | ||
* | ||
* ```js | ||
* import { resource } from 'ember-resources'; | ||
* import { modifier } from 'ember-resources/modifier'; | ||
* | ||
* const wiggle = modifier((element, arg1, arg2, namedArgs) => { | ||
* return resource(({ on }) => { | ||
* let animation = element.animate([ | ||
* { transform: `translateX(${arg1}px)` }, | ||
* { transform: `translateX(-${arg2}px)` }, | ||
* ], { | ||
* duration: 100, | ||
* iterations: Infinity, | ||
* }); | ||
* | ||
* on.cleanup(() => animation.cancel()); | ||
* }); | ||
* }); | ||
* | ||
* <template> | ||
* <div {{wiggle 2 5 named="hello"}}>hello</div> | ||
* </template> | ||
* ``` | ||
* | ||
*/ | ||
export function modifier<El extends Element, Args extends unknown[] = unknown[]>( | ||
fn: (element: El, ...args: Args) => void | ||
): ModifierLike<{ | ||
Element: El; | ||
Args: { | ||
Named: EmptyObject; | ||
Positional: Args; | ||
}; | ||
}>; | ||
|
||
/** | ||
* A resource-based API for building modifiers. | ||
* | ||
* You can attach this to an element, and use a `resource` to manage | ||
* the state, add event listeners, remove event listeners on cleanup, etc. | ||
* | ||
* Using resources for modifiers provides a clear and concise API with | ||
* easy to read concerns. | ||
* | ||
* | ||
* The signature for the modifier here is _different_ from `ember-modifier`, where positional args and named args are grouped together into an array and object respectively. | ||
* This signature for ember-resource's `modifier` follows the [plain function invocation](https://blog.emberjs.com/plain-old-functions-as-helpers/) signature. | ||
* | ||
* ```js | ||
* import { resource } from 'ember-resources'; | ||
* import { modifier } from 'ember-resources/modifier'; | ||
* | ||
* const wiggle = modifier((element, arg1, arg2, namedArgs) => { | ||
* return resource(({ on }) => { | ||
* let animation = element.animate([ | ||
* { transform: `translateX(${arg1}px)` }, | ||
* { transform: `translateX(-${arg2}px)` }, | ||
* ], { | ||
* duration: 100, | ||
* iterations: Infinity, | ||
* }); | ||
* | ||
* on.cleanup(() => animation.cancel()); | ||
* }); | ||
* }); | ||
* | ||
* <template> | ||
* <div {{wiggle 2 5 named="hello"}}>hello</div> | ||
* </template> | ||
* ``` | ||
* | ||
*/ | ||
export function modifier<S extends { Element?: Element }>( | ||
fn: (element: ElementFor<S>, ...args: ArgsForFn<S>) => ReturnType<typeof resource> | ||
): ModifierLike<S>; | ||
/** | ||
* A resource-based API for building modifiers. | ||
* | ||
* You can attach this to an element, and use a `resource` to manage | ||
* the state, add event listeners, remove event listeners on cleanup, etc. | ||
* | ||
* Using resources for modifiers provides a clear and concise API with | ||
* easy to read concerns. | ||
* | ||
* | ||
* The signature for the modifier here is _different_ from `ember-modifier`, where positional args and named args are grouped together into an array and object respectively. | ||
* This signature for ember-resource's `modifier` follows the [plain function invocation](https://blog.emberjs.com/plain-old-functions-as-helpers/) signature. | ||
* | ||
* ```js | ||
* import { resource } from 'ember-resources'; | ||
* import { modifier } from 'ember-resources/modifier'; | ||
* | ||
* const wiggle = modifier((element, arg1, arg2, namedArgs) => { | ||
* return resource(({ on }) => { | ||
* let animation = element.animate([ | ||
* { transform: `translateX(${arg1}px)` }, | ||
* { transform: `translateX(-${arg2}px)` }, | ||
* ], { | ||
* duration: 100, | ||
* iterations: Infinity, | ||
* }); | ||
* | ||
* on.cleanup(() => animation.cancel()); | ||
* }); | ||
* }); | ||
* | ||
* <template> | ||
* <div {{wiggle 2 5 named="hello"}}>hello</div> | ||
* </template> | ||
* ``` | ||
* | ||
*/ | ||
export function modifier<S extends { Args?: object }>( | ||
fn: (element: ElementFor<S>, ...args: ArgsForFn<S>) => ReturnType<typeof resource> | ||
): ModifierLike<S>; | ||
/** | ||
* A resource-based API for building modifiers. | ||
* | ||
* You can attach this to an element, and use a `resource` to manage | ||
* the state, add event listeners, remove event listeners on cleanup, etc. | ||
* | ||
* Using resources for modifiers provides a clear and concise API with | ||
* easy to read concerns. | ||
* | ||
* | ||
* The signature for the modifier here is _different_ from `ember-modifier`, where positional args and named args are grouped together into an array and object respectively. | ||
* This signature for ember-resource's `modifier` follows the [plain function invocation](https://blog.emberjs.com/plain-old-functions-as-helpers/) signature. | ||
* | ||
* ```js | ||
* import { resource } from 'ember-resources'; | ||
* import { modifier } from 'ember-resources/modifier'; | ||
* | ||
* const wiggle = modifier((element, arg1, arg2, namedArgs) => { | ||
* return resource(({ on }) => { | ||
* let animation = element.animate([ | ||
* { transform: `translateX(${arg1}px)` }, | ||
* { transform: `translateX(-${arg2}px)` }, | ||
* ], { | ||
* duration: 100, | ||
* iterations: Infinity, | ||
* }); | ||
* | ||
* on.cleanup(() => animation.cancel()); | ||
* }); | ||
* }); | ||
* | ||
* <template> | ||
* <div {{wiggle 2 5 named="hello"}}>hello</div> | ||
* </template> | ||
* ``` | ||
* | ||
*/ | ||
export function modifier<S extends { Element?: Element; Args?: object }>( | ||
fn: (element: ElementFor<S>, ...args: ArgsForFn<S>) => ReturnType<typeof resource> | ||
): ModifierLike<S>; | ||
|
||
export function modifier(fn: (element: Element, ...args: unknown[]) => void): ModifierLike<{ | ||
Element: Element; | ||
Args: { | ||
Named: {}; | ||
Positional: []; | ||
}; | ||
}> { | ||
assert(`modifier() must be invoked with a function`, typeof fn === 'function'); | ||
setModifierManager(() => MANAGER, fn); | ||
resourceFactory(fn); | ||
|
||
return fn as unknown as ModifierLike<{ | ||
Element: Element; | ||
Args: { | ||
Named: {}; | ||
Positional: []; | ||
}; | ||
}>; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type FunctionBasedModifierDefinition<S> = ( | ||
element: ElementFor<S>, | ||
positional: PositionalArgs<S>, | ||
named: NamedArgs<S> | ||
) => void; |
Oops, something went wrong.