Skip to content

Commit

Permalink
Add an opt-in property for delayed value binding (#68)
Browse files Browse the repository at this point in the history
Add an override to `bind` named `bfDelayValue` that is schedulable and
schedules to the key `value`.

Also, rewrite the documentation to rename "default scheduled" to
"delayed scheduled" while still emphasizing its default nature. This
should clear up some confusion as well as provide a better name
long-term.

Resolves #38
  • Loading branch information
WorldMaker authored Jan 4, 2024
2 parents 67c9a19 + 85bf93c commit 862ba3f
Show file tree
Hide file tree
Showing 23 changed files with 194 additions and 107 deletions.
16 changes: 15 additions & 1 deletion binding.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
bufferEntries,
makeEntries,
schedulable,
scheduledKey,
} from './binding.js'
import { ElementDescription } from './component.js'
import { jsx } from './jsx.js'
Expand All @@ -26,10 +27,23 @@ describe('binding', () => {
deepEqual(actual, expected)
})

it('schedules bfDelayValue as value', () => {
const key = 'bfDelayValue'
const actual = schedulable(key, false)
const expected = true
deepEqual(actual, expected)
const actualKey = scheduledKey(key)
const expectedKey = 'value'
deepEqual(actualKey, expectedKey)
})

it('considers other keys schedulable', () => {
const actual = schedulable('example', false)
const key = 'example'
const actual = schedulable(key, false)
const expected = true
deepEqual(actual, expected)
const actualKey = scheduledKey(key)
deepEqual(actualKey, key)
})

it('makes entry observables', () => {
Expand Down
9 changes: 8 additions & 1 deletion binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ export function schedulable(key: string | number | symbol, immediate: boolean) {
return !(immediate || key === 'value')
}

export function scheduledKey(key: string | number | symbol) {
if (key === 'bfDelayValue') {
return 'value'
}
return key
}

export function makeEntries(
key: string | number | symbol,
observable: Observable<unknown>,
Expand All @@ -192,7 +199,7 @@ function bindElementBinds(

for (const [key, observable, immediate] of binds) {
if (schedulable(key, immediate)) {
schedulables.push([key, observable] as ObservableEntry)
schedulables.push([scheduledKey(key), observable] as ObservableEntry)
} else {
subscription.add(bindObjectKey(element, key, observable, error, complete))
}
Expand Down
14 changes: 13 additions & 1 deletion component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export type ButterfloatAttributes = HtmlAttributes & ChildrenBindable

export type DefaultBind = Record<string, Observable<unknown>>

export interface DelayBind {
/**
* Delay scheduled binding for the "value" property.
*
* Value is bound immediately by default to avoid user interaction
* problems. This provides an opt-in for tested interaction patterns
* and rare elements that use "value" for things aren't user
* interaction such as <progress />.
*/
bfDelayValue?: Observable<unknown>
}

export type DefaultStyleBind = Record<string, Observable<unknown>>

export type ClassBind = Record<string, Observable<boolean>>
Expand All @@ -69,7 +81,7 @@ export interface ButterfloatIntrinsicAttributes<
*
* May use an non-immediate scheduler. Obvious exception: all "value" bindings are immediate, given their role in user inputs.
*/
bind?: Bind
bind?: Bind & DelayBind
/**
* Immediately bind an observable to a DOM property
*/
Expand Down
24 changes: 15 additions & 9 deletions docs/suspense.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ on several types of binding by this point, we should be able to dig
into some of the meatier aspects of Butterfloat's binding mechanics
and some of its advanced features, especially Suspense binding.

## Default Scheduling versus Immediate Scheduling
## Default Scheduling (Delayed Scheduling) versus Immediate Scheduling

There are two common "flavors" of binds in Butterfloat: default and
immediate. The primary places you see these flavors are in the JSX
`bind` versus `immediateBind` attributes, `classBind` versus
`immediateClassBind`, `styleBind` versus `immediateStyleBind`,
There are two common "flavors" of binds in Butterfloat: delayed (which is
largely the default) and immediate. The primary places you see these
flavors are in the JSX `bind` versus `immediateBind` attributes, `classBind`
versus `immediateClassBind`, `styleBind` versus `immediateStyleBind`,
and in the `ComponentContext` the differences between `bindEffect`
and `bindImmediateEffect` helpers.

The immediate flavor gets the longer names because it shouldn't be
the default. The default scheduling flavor tries to be smarter by
the default. The delayed scheduling flavor tries to be smarter by
default.

There's one obvious exception to note that fields named `value` are
Expand All @@ -26,7 +26,13 @@ space where delays would be most noticeable to users and most
detrimental to user interaction. (Though the advice here is that
you should consider twice if you need to bind an input's value.)

Default scheduling exists to orchestrate bound changes to hopefully
If you do want to opt-in to a delay scheduled `value` field, such
as for elements that use `value` outside of direct user interaction
such as `<progress />` elements, or in cases where you have tested
and are careful of the user interaction repercussions, you can use
the `bfDelayValue` special key to the default `bind` attribute.

Delayed scheduling exists to orchestrate bound changes to hopefully
maximize responsiveness of the application. The general basics are
that changes are aggregated and buffered so that they happen no
_faster_ than `requestAnimationFrame` time. This allows the browser
Expand All @@ -36,8 +42,8 @@ noticeable "churn" of changes from a user's perspective (very basic
debouncing/throttling), and increase the priorities of user
interaction responsiveness.

It is suggested to start with default scheduling and then as a
developer where you learn that some updates provide a better
It is suggested to start with delayed scheduling as your default and
then as a developer where you learn that some updates provide a better
user experience when immediately bound, you can move those bindings
to the appropriate `immediateBind` or `bindImmediateEffect`.

Expand Down
2 changes: 1 addition & 1 deletion docs/types/interfaces/ButterfloatEvents.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ boundaries to vanilla JS components.

#### Defined in

[events.ts:12](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/events.ts#L12)
[events.ts:12](https://github.com/WorldMaker/butterfloat/blob/75c28b8/events.ts#L12)
20 changes: 10 additions & 10 deletions docs/types/interfaces/ButterfloatIntrinsicAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@

### bind

`Optional` **bind**: `Bind`
`Optional` **bind**: `Bind` & [`DelayBind`](DelayBind.md)

Bind an observable to an DOM property.

May use an non-immediate scheduler. Obvious exception: all "value" bindings are immediate, given their role in user inputs.

#### Defined in

[component.ts:72](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L72)
[component.ts:84](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L84)

___

Expand All @@ -58,7 +58,7 @@ ButterfloatAttributes.childrenBind

#### Defined in

[component.ts:47](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L47)
[component.ts:47](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L47)

___

Expand All @@ -74,7 +74,7 @@ ButterfloatAttributes.childrenBindMode

#### Defined in

[component.ts:51](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L51)
[component.ts:51](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L51)

___

Expand All @@ -86,7 +86,7 @@ Bind a boolean observable to the appearance of a class in classList.

#### Defined in

[component.ts:92](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L92)
[component.ts:104](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L104)

___

Expand All @@ -98,7 +98,7 @@ Bind an event observable to a DOM event.

#### Defined in

[component.ts:80](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L80)
[component.ts:92](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L92)

___

Expand All @@ -110,7 +110,7 @@ Immediately bind an observable to a DOM property

#### Defined in

[component.ts:76](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L76)
[component.ts:88](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L88)

___

Expand All @@ -122,7 +122,7 @@ Immediately bind a boolean observable to the appearance of a class in classList.

#### Defined in

[component.ts:96](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L96)
[component.ts:108](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L108)

___

Expand All @@ -134,7 +134,7 @@ Immediately bind an observable to a style property.

#### Defined in

[component.ts:88](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L88)
[component.ts:100](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L100)

___

Expand All @@ -146,4 +146,4 @@ Bind an observable to a style property.

#### Defined in

[component.ts:84](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L84)
[component.ts:96](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L96)
6 changes: 3 additions & 3 deletions docs/types/interfaces/ChildrenBindDescription.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

#### Defined in

[component.ts:112](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L112)
[component.ts:124](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L124)

___

Expand All @@ -38,7 +38,7 @@ ___

#### Defined in

[component.ts:113](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L113)
[component.ts:125](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L125)

___

Expand All @@ -48,4 +48,4 @@ ___

#### Defined in

[component.ts:114](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L114)
[component.ts:126](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L126)
4 changes: 2 additions & 2 deletions docs/types/interfaces/ChildrenBindable.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Bind children as they are observed.

#### Defined in

[component.ts:47](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L47)
[component.ts:47](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L47)

___

Expand All @@ -31,4 +31,4 @@ Mode in which to bind children. Defaults to 'append'.

#### Defined in

[component.ts:51](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L51)
[component.ts:51](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L51)
4 changes: 2 additions & 2 deletions docs/types/interfaces/ChildrenDescription.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#### Defined in

[component.ts:144](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L144)
[component.ts:156](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L156)

___

Expand All @@ -27,4 +27,4 @@ ___

#### Defined in

[component.ts:143](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L143)
[component.ts:155](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L155)
2 changes: 1 addition & 1 deletion docs/types/interfaces/ChildrenProperties.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ in the tree.

#### Defined in

[jsx.ts:107](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/jsx.ts#L107)
[jsx.ts:107](https://github.com/WorldMaker/butterfloat/blob/75c28b8/jsx.ts#L107)
6 changes: 3 additions & 3 deletions docs/types/interfaces/ComponentContext.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ effect binders and events proxies.

#### Defined in

[component.ts:18](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L18)
[component.ts:18](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L18)

___

Expand All @@ -37,7 +37,7 @@ ___

#### Defined in

[component.ts:19](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L19)
[component.ts:19](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L19)

___

Expand All @@ -47,4 +47,4 @@ ___

#### Defined in

[component.ts:17](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L17)
[component.ts:17](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L17)
12 changes: 6 additions & 6 deletions docs/types/interfaces/ComponentDescription.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

#### Defined in

[component.ts:112](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L112)
[component.ts:124](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L124)

___

Expand All @@ -45,7 +45,7 @@ ___

#### Defined in

[component.ts:113](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L113)
[component.ts:125](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L125)

___

Expand All @@ -59,7 +59,7 @@ ___

#### Defined in

[component.ts:114](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L114)
[component.ts:126](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L126)

___

Expand All @@ -69,7 +69,7 @@ ___

#### Defined in

[component.ts:133](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L133)
[component.ts:145](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L145)

___

Expand All @@ -79,7 +79,7 @@ ___

#### Defined in

[component.ts:134](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L134)
[component.ts:146](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L146)

___

Expand All @@ -89,4 +89,4 @@ ___

#### Defined in

[component.ts:132](https://github.com/WorldMaker/butterfloat/blob/37e9dd5/component.ts#L132)
[component.ts:144](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L144)
26 changes: 26 additions & 0 deletions docs/types/interfaces/DelayBind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[butterfloat](../README.md) / [Exports](../modules.md) / DelayBind

# Interface: DelayBind

## Table of contents

### Properties

- [bfDelayValue](DelayBind.md#bfdelayvalue)

## Properties

### bfDelayValue

`Optional` **bfDelayValue**: `Observable`\<`unknown`\>

Delay scheduled binding for the "value" property.

Value is bound immediately by default to avoid user interaction
problems. This provides an opt-in for tested interaction patterns
and rare elements that use "value" for things aren't user
interaction such as <progress />.

#### Defined in

[component.ts:67](https://github.com/WorldMaker/butterfloat/blob/75c28b8/component.ts#L67)
Loading

0 comments on commit 862ba3f

Please sign in to comment.