@weasel-js/prefs 1.9.4 → 1.10.1

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/README.md CHANGED
@@ -9,6 +9,22 @@ store.get('view.gridVisible');
9
9
  store.set('view.gridVisible', false);
10
10
  ```
11
11
 
12
+ A schema is plain data, and `pref` writes it shorter:
13
+
14
+ ```ts
15
+ const SCHEMA = pref.group('Prefs', {
16
+ view: pref.group('View', {
17
+ gridVisible: pref.boolean('Grid', true),
18
+ gridGap: pref.number('Grid gap', 8, { min: 2, max: 64, unit: { scale: 1, suffix: 'px' } }),
19
+ mode: pref.enum('Mode', 'dark', ['dark', 'light']),
20
+ }),
21
+ });
22
+ ```
23
+
24
+ Each builder returns what the literal would hold (`{ name, children }` for a
25
+ group, `{ kind, name, description, default, … }` for a leaf), so paths and
26
+ value types are read from it the same way, and the two can be mixed.
27
+
12
28
  One record per leaf. A leaf with no record follows its schema default. Reads
13
29
  repair stored values that no longer fit the schema without writing them back.
14
30
  `migrations[i]` takes stored records from version `i` to `i + 1`.
@@ -19,6 +35,13 @@ repair stored values that no longer fit the schema without writing them back.
19
35
  live in `@weasel-js/prefs/react`, so the main entry never loads React.
20
36
  `usePrefsValues` returns the pieces `@weasel-js/ui`'s `PrefsForm` takes.
21
37
 
38
+ `usePrefsStore(schema, { storage, prefix })` gives a component a store of its
39
+ own: open from its first render, closed with its writes sent when it unmounts.
40
+ It takes an adapter that reads synchronously, and components naming the same
41
+ `storage`, `prefix`, and `schema` share one store. Components naming one
42
+ `storage` and `prefix` with different schemas each get a store over the same
43
+ records.
44
+
22
45
  ## Ranges
23
46
 
24
47
  A number leaf's range is one scale: the low end is the least of what its label
@@ -28,3 +51,87 @@ draws it there. Put an unlimited setting at the end of the range it is nearest:
28
51
  a 0–100% limit is off at 100, and a scale with no such end takes `endless`,
29
52
  which adds a stop one step past the range that stores `Infinity` and reads the
30
53
  leaf's `infinity` word.
54
+
55
+ ## Split values
56
+
57
+ A leaf that lists `splitBy` may hold its value once per option of another
58
+ leaf, where it would hold one. The other leaf, the selector, is an ordinary
59
+ `enum` named by its path:
60
+
61
+ ```ts
62
+ 'look.mode': { kind: 'enum', default: 'dark', options: [dark, light], … }
63
+ 'gap': { kind: 'number', default: 4, splitBy: ['look.mode'], … }
64
+
65
+ store.split('gap', 'look.mode'); // holds { $by: 'look.mode', $of: { dark: 4, light: 4 } }
66
+ store.set('gap', 8); // writes the member the mode is set to
67
+ store.set('look.mode', 'light'); // a change to every leaf split by it
68
+ store.join('gap', 'look.mode'); // back to one value: the member the mode is set to
69
+ ```
70
+
71
+ The stored value is a locus, `{ $by, $of }`, and a member may itself be split
72
+ by a second selector. `store.get` reads the member the selectors are set to.
73
+ `store.get(path, { weights })` reads the alternatives folded by weight, which
74
+ is what a transition between two options shows on the way:
75
+
76
+ ```ts
77
+ store.get('gap', { weights: { 'look.mode': { dark: 0.25, light: 0.75 } } });
78
+ ```
79
+
80
+ A number blends linearly and a color blends in OKLCH. Every other kind takes
81
+ its heaviest alternative, the first option on a tie; `blends` in the open
82
+ options says otherwise for a kind. `store.values({ at: { 'look.mode': 'light' } })`
83
+ is the whole tree as it would read with the selectors set as given.
84
+
85
+ `store.set(path, value, { at })` writes the member a stated choice names,
86
+ whatever the selectors are set to, and `store.loci()` is what every split leaf
87
+ holds, by path: one map until something changes. `usePrefsValues` returns
88
+ both shaped for `PrefsForm`, with `split` and `join` beside them:
89
+
90
+ ```tsx
91
+ const { values, set, loci, split, join, setAt } = usePrefsValues(store);
92
+ <PrefsForm schema={schema} values={values} onChange={set}
93
+ loci={loci} onSplit={split} onJoin={join} onChangeAt={setAt} />
94
+ ```
95
+
96
+ A leaf's default may be split too. `defaultLocus` is what the leaf holds while
97
+ nothing is stored for it, and `default` stays what a member it leaves out
98
+ reads as:
99
+
100
+ ```ts
101
+ 'gap': { kind: 'number', default: 4, splitBy: ['look.mode'],
102
+ defaultLocus: { $by: 'look.mode', $of: { dark: 4, light: 6 } }, … }
103
+
104
+ store.get('gap'); // 4, or 6 once the mode is light
105
+ store.set('gap', 8, { at: { 'look.mode': 'light' } }); // holds { dark: 4, light: 8 }
106
+ store.reset('gap'); // the split default again
107
+ ```
108
+
109
+ A selector must be a plain `enum`: not `clearable`, with no `encoding`, and
110
+ not split itself. The store warns of a `splitBy` that names anything else when
111
+ it opens.
112
+
113
+ ## As CSS custom properties
114
+
115
+ `prefsCss(store)` writes the store's values as custom properties, and
116
+ `prefsCssAttributes(store)` gives the attributes that pick its blocks:
117
+
118
+ ```css
119
+ :where(:root) {
120
+ --look-mode: dark;
121
+ --gap: 4px; /* a split value, at the member the selectors' defaults name */
122
+ }
123
+
124
+ :where(:root[data-look-mode='light']) {
125
+ --look-mode: light;
126
+ --gap: 6px;
127
+ }
128
+ ```
129
+
130
+ Every leaf is set on the root. A block follows for each choice of options a
131
+ split value tells apart, and only those: a value that is one thing under
132
+ `light` at every density gets one block for `light`. Every block is inside
133
+ `:where()`, so a rule of the page's own wins, and blocks naming more selectors
134
+ come later. A number is written in its display unit where the unit's suffix is
135
+ a CSS unit, a boolean as `1` or `0`, an enum's option as an identifier, and a
136
+ string quoted; a leaf of any other kind is left out unless the `value` option
137
+ writes it. `root`, `property`, and `attribute` rename what it writes.