@ozio/spicerack 0.1.0-beta.11

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 ADDED
@@ -0,0 +1,449 @@
1
+ # Spicerack
2
+
3
+ Spicerack is a small, dat.GUI-inspired controls panel for inspecting and changing values in a web application.
4
+
5
+ Spicerack uses Vue internally, but its consumer API is ordinary JavaScript. Applications do not need to create Vue components, install the Vue Vite plugin, or mount a Vue app.
6
+
7
+ ```js
8
+ import { createSpicerack } from '@ozio/spicerack';
9
+
10
+ const gui = createSpicerack([
11
+ { key: 'name', value: 'Nicholas', label: 'Name' },
12
+ { key: 'points', value: 32, label: 'Points' },
13
+ { key: 'enabled', value: true, label: 'Enabled' }
14
+ ]);
15
+
16
+ gui.mount('#spicerack');
17
+ ```
18
+
19
+ ## Installation
20
+
21
+ ### npm
22
+
23
+ ```sh
24
+ npm install @ozio/spicerack
25
+ ```
26
+
27
+ ```js
28
+ import { createSpicerack } from '@ozio/spicerack';
29
+ ```
30
+
31
+ Vue and the built-in controls are bundled implementation details of `@ozio/spicerack`; consumers do not install or import them directly.
32
+
33
+ ### jsDelivr (ESM)
34
+
35
+ ```html
36
+ <div id="spicerack"></div>
37
+
38
+ <script type="module">
39
+ import { createSpicerack } from 'https://cdn.jsdelivr.net/npm/@ozio/spicerack/+esm';
40
+
41
+ const gui = createSpicerack([
42
+ { key: 'message', value: 'Hello', label: 'Message' }
43
+ ]);
44
+
45
+ gui.mount('#spicerack');
46
+ </script>
47
+ ```
48
+
49
+ Pin a package version in production if reproducible builds are important:
50
+
51
+ ```js
52
+ import { createSpicerack } from 'https://cdn.jsdelivr.net/npm/@ozio/spicerack@0.1.0-beta.7/+esm';
53
+ ```
54
+
55
+ ## Sample usage
56
+
57
+ ### Defining a config
58
+
59
+ Pass a config array and optional settings to `createSpicerack(config, options)`. A control's `type` can be omitted for string, number, and boolean values.
60
+
61
+ ```js
62
+ import { createSpicerack } from '@ozio/spicerack';
63
+
64
+ const gui = createSpicerack([
65
+ {
66
+ type: 'folder',
67
+ label: 'Player',
68
+ open: true,
69
+ config: [
70
+ {
71
+ key: 'name',
72
+ value: 'LeBron James',
73
+ label: 'Name',
74
+ maxlength: 40
75
+ },
76
+ {
77
+ key: 'points',
78
+ type: 'range',
79
+ value: 32,
80
+ label: 'Points',
81
+ min: 0,
82
+ max: 100,
83
+ step: 1
84
+ }
85
+ ]
86
+ },
87
+ {
88
+ key: 'conference',
89
+ type: 'list',
90
+ value: '',
91
+ label: 'Conference',
92
+ options: [
93
+ { value: '', label: 'All Conferences' },
94
+ { value: 'east', label: 'Eastern' },
95
+ { value: 'west', label: 'Western' }
96
+ ]
97
+ },
98
+ {
99
+ type: 'button',
100
+ label: 'Reset',
101
+ onClick(event) {
102
+ console.log('Reset clicked', event);
103
+ }
104
+ }
105
+ ], {
106
+ title: 'Player Settings',
107
+ theme: 'dark',
108
+ position: 'top-right',
109
+ open: true
110
+ });
111
+ ```
112
+
113
+ Spicerack currently supports one folder level. Do not put a folder inside another folder.
114
+
115
+ ### Mounting to the DOM
116
+
117
+ Construction and mounting are separate operations:
118
+
119
+ ```js
120
+ const gui = createSpicerack(config);
121
+
122
+ gui.mount('#spicerack');
123
+ ```
124
+
125
+ `mount()` accepts a CSS selector or an `Element`. If no target is supplied, it mounts to `document.body`.
126
+
127
+ ```js
128
+ gui.mount(document.querySelector('#spicerack'));
129
+ ```
130
+
131
+ An instance currently supports one application mount in normal use. An explicit public `unmount()` API is not yet available.
132
+
133
+ ### Reading and changing values
134
+
135
+ Each keyed value control is available through `gui.model`:
136
+
137
+ ```js
138
+ console.log(gui.model.points.value);
139
+
140
+ gui.model.points.value = 50;
141
+ ```
142
+
143
+ Calling `gui.json()` returns a plain snapshot of all model values:
144
+
145
+ ```js
146
+ console.log(gui.json());
147
+ // { name: 'LeBron James', points: 50, conference: '' }
148
+ ```
149
+
150
+ Folders and buttons are structural or action controls and are not added to the model. During the current beta, a divider still requires a key and creates an `undefined` model field; this is tracked as a structural-control issue.
151
+
152
+ ### Adding event handlers
153
+
154
+ Subscribe to model changes with `on('change', callback)`. This observes both user input and programmatic assignments.
155
+
156
+ ```js
157
+ const stop = gui.model.points.on('change', (value, previousValue) => {
158
+ console.log({ value, previousValue });
159
+ });
160
+
161
+ gui.model.points.value = 51;
162
+
163
+ stop();
164
+ ```
165
+
166
+ Control-level listeners can also be supplied with Vue-style listener prop names. Value controls emit the new domain value followed by the original DOM event:
167
+
168
+ ```js
169
+ const config = [
170
+ {
171
+ key: 'name',
172
+ value: 'Nicholas',
173
+ label: 'Name',
174
+ onChange(value, event) {
175
+ console.log(value, event);
176
+ }
177
+ },
178
+ {
179
+ type: 'button',
180
+ label: 'Save',
181
+ onClick(event) {
182
+ console.log('Save', event);
183
+ }
184
+ }
185
+ ];
186
+ ```
187
+
188
+ Prefer model subscriptions when application behavior should also react to programmatic value changes.
189
+
190
+ ## API
191
+
192
+ ### `createSpicerack(config, options?)`
193
+
194
+ Creates and returns a Spicerack instance. It does not mount anything during construction.
195
+
196
+ ### Config
197
+
198
+ The first argument is an array of config records. Spicerack reserves `key`, `type`, `value`, and `config`; other properties are forwarded to the selected control as props or event listeners.
199
+
200
+ | Property | Type | Description |
201
+ | --- | --- | --- |
202
+ | `key` | `string` | Unique model key. Required for value controls. |
203
+ | `type` | `string` | Registered control type. Optional when inferred from a string, number, or boolean value. |
204
+ | `value` | `unknown` | Initial value for a value control. |
205
+ | `label` | `string` | Visible control label. |
206
+ | `config` | `SpicerackConfig[]` | Child controls for a folder. One folder level is supported. |
207
+ | Additional properties | `unknown` | Forwarded to the selected control, such as `min`, `max`, `step`, `options`, or `onChange`. |
208
+
209
+ Keys must be non-empty strings and unique across the entire config, including controls inside folders. Buttons do not require a key or value.
210
+
211
+ ### Options
212
+
213
+ | Option | Type | Default | Description |
214
+ | --- | --- | --- | --- |
215
+ | `controls` | `Record<string, SpicerackControl>` | `{}` | Custom controls added to the registry. A matching name can override a built-in control. |
216
+ | `tokens` | `SpicerackTokens` | Built-in theme | Style-token overrides. |
217
+ | `open` | `boolean` | Persisted value or `true` | Initial expanded state. |
218
+ | `theme` | `'light' \| 'dark'` | Persisted value or `'dark'` | Initial color theme. |
219
+ | `position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | Persisted value or `'top-right'` | Initial panel corner. |
220
+ | `title` | `string` | `''` | Header title. |
221
+
222
+ An explicit option takes precedence over a persisted value. Panel state is currently stored in `localStorage` with the keys `sr:open`, `sr:theme`, and `sr:corner`.
223
+
224
+ ### Return object
225
+
226
+ #### `mount(target?)`
227
+
228
+ Mounts the panel to a selector or `Element`. The default target is `document.body`.
229
+
230
+ #### `model`
231
+
232
+ A null-prototype object containing one field per keyed value control. Each field exposes:
233
+
234
+ - `value`: get or set the current value.
235
+ - `on('change', callback)`: observe value changes and receive an unsubscribe function.
236
+
237
+ #### `json()`
238
+
239
+ Returns a plain object containing the current value of every model field.
240
+
241
+ #### `version`
242
+
243
+ The installed Spicerack package version as a string.
244
+
245
+ ## Built-in controls
246
+
247
+ | Type | Value | Additional props | Events |
248
+ | --- | --- | --- | --- |
249
+ | `boolean` | `boolean` | `label` | `change(value, event)` |
250
+ | `button` | None | `label` | `click(event)` |
251
+ | `divider` | None | None | None |
252
+ | `folder` | None | `label`, `open`, `config` | `click(event)` |
253
+ | `list` | Any option value | `label`, `options` | `change(value, event)` |
254
+ | `number` | `number` | `label`, `min`, `max` | `change(value, event)` |
255
+ | `range` | `number` | `label`, `min`, `max`, `step` | `input(value, event)`, `change(value, event)` |
256
+ | `switch` | `boolean` | `label` | `change(value, event)` |
257
+ | `text` | `string` | `label`, `minlength`, `maxlength` | `change(value, event)` |
258
+ | `textarea` | `string` | `label`, `minlength`, `maxlength`, `rows`, `resize` | `change(value, event)` |
259
+ | `toggle` | `boolean` | `label` | `change(value, event)` |
260
+
261
+ The shared control contract is still evolving. In particular, a common `disabled` prop is not yet implemented across every interactive control.
262
+
263
+ During the current beta, divider config records require a unique `key` even though dividers have no value. This requirement is expected to be removed.
264
+
265
+ ## Custom controls
266
+
267
+ Custom controls are compiled Vue components registered through the framework-neutral Spicerack options object. Consumers of your control package still interact with Spicerack through ordinary JavaScript.
268
+
269
+ ```js
270
+ import { createSpicerack } from '@ozio/spicerack';
271
+ import { FancyControl } from 'fancy-spicerack-controls';
272
+
273
+ const gui = createSpicerack([
274
+ {
275
+ key: 'nickname',
276
+ type: 'fancy',
277
+ value: 'King',
278
+ label: 'Nickname',
279
+ emphasis: 'strong'
280
+ }
281
+ ], {
282
+ controls: {
283
+ fancy: FancyControl
284
+ }
285
+ });
286
+
287
+ gui.mount('#spicerack');
288
+ ```
289
+
290
+ See the [built-in controls source](https://github.com/ortenzio/spicerack/tree/main/packages/controls/src) for working component examples.
291
+
292
+ ### Package dependencies
293
+
294
+ A third-party control package should treat Vue as a peer dependency and include the build tooling it uses as development dependencies:
295
+
296
+ ```json
297
+ {
298
+ "peerDependencies": {
299
+ "vue": "^3.5.40"
300
+ },
301
+ "devDependencies": {
302
+ "@vitejs/plugin-vue": "^6.0.0",
303
+ "vite": "^8.2.0",
304
+ "vite-plugin-css-injected-by-js": "^5.0.2",
305
+ "vue": "^3.5.40"
306
+ }
307
+ }
308
+ ```
309
+
310
+ ### Vite config
311
+
312
+ This configuration matches the current built-in control packaging strategy. Vue is externalized so the control package uses Spicerack's runtime:
313
+
314
+ ```js
315
+ import { resolve } from 'node:path';
316
+ import { defineConfig } from 'vite';
317
+ import vue from '@vitejs/plugin-vue';
318
+ import cssInjectedByJs from 'vite-plugin-css-injected-by-js';
319
+
320
+ export default defineConfig({
321
+ plugins: [
322
+ vue(),
323
+ cssInjectedByJs()
324
+ ],
325
+ build: {
326
+ lib: {
327
+ entry: resolve(import.meta.dirname, 'index.js'),
328
+ formats: ['es'],
329
+ fileName: 'index'
330
+ },
331
+ rolldownOptions: {
332
+ external: ['vue']
333
+ }
334
+ }
335
+ });
336
+ ```
337
+
338
+ If you extract CSS instead, document the stylesheet import that consumers must add.
339
+
340
+ ### Vue SFC contract
341
+
342
+ Value controls receive `modelValue` and should emit `update:modelValue`. Vue's `defineModel()` macro handles both:
343
+
344
+ ```vue
345
+ <template>
346
+ <label data-sr-control="fancy">
347
+ <span v-if="label" data-sr-label>{{ label }}</span>
348
+ <input
349
+ v-model="model"
350
+ :disabled="disabled"
351
+ @change="handleChange"
352
+ />
353
+ </label>
354
+ </template>
355
+
356
+ <script setup>
357
+ defineProps({
358
+ label: { type: String, default: null },
359
+ disabled: { type: Boolean, default: false }
360
+ });
361
+
362
+ const model = defineModel();
363
+ const emit = defineEmits(['change']);
364
+
365
+ function handleChange (event) {
366
+ emit('change', model.value, event);
367
+ }
368
+ </script>
369
+ ```
370
+
371
+ Action controls should emit `click` and do not need to define a model.
372
+
373
+ ### Style directives
374
+
375
+ Spicerack provides low-specificity structural styles for these attributes:
376
+
377
+ - `[data-sr-control]`: root element for a control.
378
+ - `[data-sr-label]`: shared label typography.
379
+ - `[data-sr-spacer]`: flexible spacer used in horizontal controls.
380
+
381
+ Controls can use Spicerack's CSS custom properties, including:
382
+
383
+ - `--sr-bg-app`, `--sr-bg-control`, `--sr-bg-accent`
384
+ - `--sr-fg-app`, `--sr-fg-elm`, `--sr-fg-muted`
385
+ - `--sr-border-app`, `--sr-border-active`
386
+ - `--sr-fs-label`, `--sr-fs-input`
387
+ - `--sr-control-radius`, `--sr-control-gap`
388
+
389
+ Treat these as part of the current authoring environment, but expect the design-token surface to evolve during beta releases.
390
+
391
+ ## Style tokens
392
+
393
+ Pass token overrides through `options.tokens`:
394
+
395
+ ```js
396
+ const gui = createSpicerack(config, {
397
+ tokens: {
398
+ appWidth: '20rem',
399
+ appZIndex: 1000,
400
+ controlRadius: '0.5rem',
401
+ bgColorAccent: 'rebeccapurple'
402
+ }
403
+ });
404
+ ```
405
+
406
+ Available keys:
407
+
408
+ - Layout: `appEdgeSpacing`, `appPadding`, `appRadius`, `appWidth`, `appZIndex`
409
+ - Controls: `controlRadius`, `controlGap`
410
+ - Typography: `fontSizeControl`, `fontSizeInput`, `fontSizeTitle`
411
+ - Backgrounds: `bgColorAccent`, `bgColorAccentHover`, `bgColorActive`, `bgColorAlt`, `bgColorApp`, `bgColorCard`, `bgColorControl`, `bgColorElm`, `bgColorMuted`
412
+ - Foregrounds: `fgColorAccent`, `fgColorActive`, `fgColorAlt`, `fgColorApp`, `fgColorCard`, `fgColorElm`, `fgColorMuted`
413
+ - Borders: `borderColorActive`, `borderColorAlt`, `borderColorApp`, `borderColorCard`, `borderColorElm`, `borderColorMuted`
414
+
415
+ CSS length and color tokens accept CSS strings. `appZIndex` accepts a number.
416
+
417
+ ## Types
418
+
419
+ The package includes declarations for `createSpicerack` and exports the public API types:
420
+
421
+ ```ts
422
+ import {
423
+ createSpicerack,
424
+ type SpicerackConfig,
425
+ type SpicerackInstance,
426
+ type SpicerackOptions,
427
+ type SpicerackTokens
428
+ } from '@ozio/spicerack';
429
+ ```
430
+
431
+ The maintained declaration file is emitted as part of the Vite library build. TypeScript is not used to compile the application.
432
+
433
+ ## Contact
434
+
435
+ - [GitHub repository](https://github.com/ortenzio/spicerack)
436
+ - [Issue tracker](https://github.com/ortenzio/spicerack/issues)
437
+
438
+ ## Contributing
439
+
440
+ Install dependencies and run the workspace checks from the repository root:
441
+
442
+ ```sh
443
+ npm install
444
+ npm run build
445
+ npm run lint -w @ozio/spicerack
446
+ npm run lint -w @ozio/spicerack-controls
447
+ ```
448
+
449
+ Keep the demo framework-agnostic: it should consume `@ozio/spicerack` as plain JavaScript and should not import Vue or use the Vue Vite plugin.