@human-synthesis/norns-ui 0.0.11 → 0.2.0
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/package.json +6 -2
- package/src/auto-import.js +18 -1
- package/src/components/Chart.n +129 -0
- package/src/components/ChatThread.n +97 -0
- package/src/components/DataTable.n +10 -2
- package/src/components/Feed.n +71 -0
- package/src/components/Kanban.n +191 -0
- package/src/components/Listbox.n +141 -0
- package/src/components/LiveLog.n +64 -0
- package/src/components/PresenceAvatars.n +45 -0
- package/src/components/Spinner.n +13 -0
- package/src/components/StreamText.n +64 -0
- package/src/components/Table.n +85 -0
- package/src/components/Timeline.n +5 -3
- package/src/components/Tree.n +4 -2
- package/src/contracts.js +272 -0
- package/src/index.js +2 -0
- package/src/lib/behaviors/index.js +1 -0
- package/src/lib/behaviors/optimistic.svelte.js +24 -0
- package/src/palette-manifest.json +4188 -0
- package/src/styles/atoms.css +8 -0
- package/src/types/Chart.d.ts +21 -0
- package/src/types/ChatThread.d.ts +27 -0
- package/src/types/DataTable.d.ts +5 -1
- package/src/types/Feed.d.ts +19 -0
- package/src/types/Kanban.d.ts +26 -0
- package/src/types/Listbox.d.ts +19 -0
- package/src/types/LiveLog.d.ts +16 -0
- package/src/types/PresenceAvatars.d.ts +17 -0
- package/src/types/Spinner.d.ts +12 -0
- package/src/types/StreamText.d.ts +18 -0
- package/src/types/Table.d.ts +29 -0
- package/src/types/Timeline.d.ts +2 -0
- package/src/types/Tree.d.ts +2 -0
- package/src/types/Window.d.ts +17 -0
package/src/contracts.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valibot props contracts for the palette components a generated Page can
|
|
3
|
+
* bind (U-02). The norns generator validates each page `components:` entry
|
|
4
|
+
* against these before emitting, so a bad binding is a structured refusal
|
|
5
|
+
* at generate time instead of a runtime surprise.
|
|
6
|
+
*
|
|
7
|
+
* A contract describes the *normalized spec entry*: the entry's first key
|
|
8
|
+
* (the component slot) is renamed to `data` when it binds a Query and to
|
|
9
|
+
* `action` when it binds an Action, matching the prop the emitter actually
|
|
10
|
+
* passes. Values are what TRON specs can hold: unit addresses for Query/
|
|
11
|
+
* Action bindings, scalars for everything else. Snippet/function props are
|
|
12
|
+
* not spec-bindable and are deliberately absent.
|
|
13
|
+
*
|
|
14
|
+
* Components without a contract here are not refused — they may be custom
|
|
15
|
+
* `.n` components, which carry their own Component-unit contract.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as v from 'valibot';
|
|
19
|
+
|
|
20
|
+
const NAME = '[A-Za-z_][A-Za-z0-9_]*';
|
|
21
|
+
export const QUERY_ADDRESS = new RegExp(`^${NAME}\\.Query\\.${NAME}$`);
|
|
22
|
+
export const ACTION_ADDRESS = new RegExp(`^${NAME}\\.Action\\.${NAME}$`);
|
|
23
|
+
export const SNIPPET_ADDRESS = new RegExp(`^${NAME}\\.Snippet\\.${NAME}$`);
|
|
24
|
+
export const STREAM_ADDRESS = new RegExp(`^${NAME}\\.Endpoint\\.${NAME}$`);
|
|
25
|
+
export const ROOM_ADDRESS = new RegExp(`^${NAME}\\.Worker\\.${NAME}$`);
|
|
26
|
+
|
|
27
|
+
export const query = () =>
|
|
28
|
+
v.pipe(v.string(), v.regex(QUERY_ADDRESS, 'expected a Query address (module.Query.name)'));
|
|
29
|
+
|
|
30
|
+
export const action = () =>
|
|
31
|
+
v.pipe(v.string(), v.regex(ACTION_ADDRESS, 'expected an Action address (module.Action.name)'));
|
|
32
|
+
|
|
33
|
+
/** A pass-through prop value — string/number/boolean literal from the spec. */
|
|
34
|
+
export const literal = () => v.union([v.string(), v.number(), v.boolean()]);
|
|
35
|
+
|
|
36
|
+
export const snippet = () =>
|
|
37
|
+
v.pipe(v.string(), v.regex(SNIPPET_ADDRESS, 'expected a Snippet address (module.Snippet.name)'));
|
|
38
|
+
|
|
39
|
+
/** A `stream:` Endpoint binding — the emitter turns it into a streamSource URL. */
|
|
40
|
+
export const stream = () =>
|
|
41
|
+
v.pipe(v.string(), v.regex(STREAM_ADDRESS, 'expected an Endpoint address (module.Endpoint.name)'));
|
|
42
|
+
|
|
43
|
+
/** A `room: true` Worker binding — the emitter turns it into a roomChannel name. */
|
|
44
|
+
export const room = () =>
|
|
45
|
+
v.pipe(v.string(), v.regex(ROOM_ADDRESS, 'expected a Worker address (module.Worker.name)'));
|
|
46
|
+
|
|
47
|
+
const opt = (schema) => v.optional(schema);
|
|
48
|
+
|
|
49
|
+
export const contracts = {
|
|
50
|
+
Table: v.strictObject({
|
|
51
|
+
data: query(),
|
|
52
|
+
columns: opt(literal()),
|
|
53
|
+
pageSize: opt(literal()),
|
|
54
|
+
striped: opt(literal()),
|
|
55
|
+
dense: opt(literal()),
|
|
56
|
+
stickyHeader: opt(literal()),
|
|
57
|
+
emptyMessage: opt(literal()),
|
|
58
|
+
cell: opt(snippet()),
|
|
59
|
+
empty: opt(snippet()),
|
|
60
|
+
class: opt(literal())
|
|
61
|
+
}),
|
|
62
|
+
DataTable: v.strictObject({
|
|
63
|
+
data: query(),
|
|
64
|
+
columns: opt(literal()),
|
|
65
|
+
striped: opt(literal()),
|
|
66
|
+
dense: opt(literal()),
|
|
67
|
+
stickyHeader: opt(literal()),
|
|
68
|
+
emptyMessage: opt(literal()),
|
|
69
|
+
sortKey: opt(literal()),
|
|
70
|
+
sortDir: opt(literal()),
|
|
71
|
+
cell: opt(snippet()),
|
|
72
|
+
empty: opt(snippet()),
|
|
73
|
+
class: opt(literal())
|
|
74
|
+
}),
|
|
75
|
+
Kanban: v.strictObject({
|
|
76
|
+
data: query(),
|
|
77
|
+
onMove: opt(action()),
|
|
78
|
+
columns: opt(literal()),
|
|
79
|
+
title: opt(literal()),
|
|
80
|
+
subtitle: opt(literal()),
|
|
81
|
+
emptyMessage: opt(literal()),
|
|
82
|
+
card: opt(snippet()),
|
|
83
|
+
empty: opt(snippet()),
|
|
84
|
+
class: opt(literal())
|
|
85
|
+
}),
|
|
86
|
+
Chart: v.strictObject({
|
|
87
|
+
data: query(),
|
|
88
|
+
type: opt(literal()),
|
|
89
|
+
x: opt(literal()),
|
|
90
|
+
y: opt(literal()),
|
|
91
|
+
label: opt(literal()),
|
|
92
|
+
labels: opt(literal()),
|
|
93
|
+
class: opt(literal())
|
|
94
|
+
}),
|
|
95
|
+
Form: v.strictObject({
|
|
96
|
+
action: action(),
|
|
97
|
+
method: opt(literal()),
|
|
98
|
+
enctype: opt(literal()),
|
|
99
|
+
class: opt(literal())
|
|
100
|
+
}),
|
|
101
|
+
Field: v.strictObject({
|
|
102
|
+
label: opt(literal()),
|
|
103
|
+
help: opt(literal()),
|
|
104
|
+
error: opt(literal()),
|
|
105
|
+
name: opt(literal()),
|
|
106
|
+
required: opt(literal()),
|
|
107
|
+
id: opt(literal()),
|
|
108
|
+
class: opt(literal())
|
|
109
|
+
}),
|
|
110
|
+
Input: v.strictObject({
|
|
111
|
+
name: opt(literal()),
|
|
112
|
+
type: opt(literal()),
|
|
113
|
+
value: opt(literal()),
|
|
114
|
+
placeholder: opt(literal()),
|
|
115
|
+
required: opt(literal()),
|
|
116
|
+
size: opt(literal()),
|
|
117
|
+
class: opt(literal())
|
|
118
|
+
}),
|
|
119
|
+
Select: v.strictObject({
|
|
120
|
+
name: opt(literal()),
|
|
121
|
+
value: opt(literal()),
|
|
122
|
+
required: opt(literal()),
|
|
123
|
+
class: opt(literal())
|
|
124
|
+
}),
|
|
125
|
+
Btn: v.strictObject({
|
|
126
|
+
variant: opt(literal()),
|
|
127
|
+
size: opt(literal()),
|
|
128
|
+
type: opt(literal()),
|
|
129
|
+
icon: opt(literal()),
|
|
130
|
+
href: opt(literal()),
|
|
131
|
+
disabled: opt(literal()),
|
|
132
|
+
class: opt(literal())
|
|
133
|
+
}),
|
|
134
|
+
Card: v.strictObject({
|
|
135
|
+
padded: opt(literal()),
|
|
136
|
+
interactive: opt(literal()),
|
|
137
|
+
href: opt(literal()),
|
|
138
|
+
class: opt(literal())
|
|
139
|
+
}),
|
|
140
|
+
Badge: v.strictObject({
|
|
141
|
+
variant: opt(literal()),
|
|
142
|
+
size: opt(literal()),
|
|
143
|
+
class: opt(literal())
|
|
144
|
+
}),
|
|
145
|
+
Banner: v.strictObject({
|
|
146
|
+
variant: opt(literal()),
|
|
147
|
+
icon: opt(literal()),
|
|
148
|
+
class: opt(literal())
|
|
149
|
+
}),
|
|
150
|
+
Pagination: v.strictObject({
|
|
151
|
+
page: opt(literal()),
|
|
152
|
+
total: opt(literal()),
|
|
153
|
+
pageSize: opt(literal()),
|
|
154
|
+
siblingCount: opt(literal()),
|
|
155
|
+
class: opt(literal())
|
|
156
|
+
}),
|
|
157
|
+
Checkbox: v.strictObject({
|
|
158
|
+
name: opt(literal()),
|
|
159
|
+
checked: opt(literal()),
|
|
160
|
+
required: opt(literal()),
|
|
161
|
+
disabled: opt(literal()),
|
|
162
|
+
error: opt(literal()),
|
|
163
|
+
class: opt(literal())
|
|
164
|
+
}),
|
|
165
|
+
Switch: v.strictObject({
|
|
166
|
+
name: opt(literal()),
|
|
167
|
+
checked: opt(literal()),
|
|
168
|
+
required: opt(literal()),
|
|
169
|
+
disabled: opt(literal()),
|
|
170
|
+
error: opt(literal()),
|
|
171
|
+
class: opt(literal())
|
|
172
|
+
}),
|
|
173
|
+
Textarea: v.strictObject({
|
|
174
|
+
name: opt(literal()),
|
|
175
|
+
value: opt(literal()),
|
|
176
|
+
placeholder: opt(literal()),
|
|
177
|
+
rows: opt(literal()),
|
|
178
|
+
required: opt(literal()),
|
|
179
|
+
disabled: opt(literal()),
|
|
180
|
+
error: opt(literal()),
|
|
181
|
+
class: opt(literal())
|
|
182
|
+
}),
|
|
183
|
+
DatePicker: v.strictObject({
|
|
184
|
+
name: opt(literal()),
|
|
185
|
+
value: opt(literal()),
|
|
186
|
+
min: opt(literal()),
|
|
187
|
+
max: opt(literal()),
|
|
188
|
+
placeholder: opt(literal()),
|
|
189
|
+
label: opt(literal()),
|
|
190
|
+
disabled: opt(literal()),
|
|
191
|
+
error: opt(literal()),
|
|
192
|
+
id: opt(literal()),
|
|
193
|
+
class: opt(literal())
|
|
194
|
+
}),
|
|
195
|
+
Uploader: v.strictObject({
|
|
196
|
+
multiple: opt(literal()),
|
|
197
|
+
accept: opt(literal()),
|
|
198
|
+
disabled: opt(literal()),
|
|
199
|
+
placeholder: opt(literal()),
|
|
200
|
+
helper: opt(literal()),
|
|
201
|
+
class: opt(literal())
|
|
202
|
+
}),
|
|
203
|
+
Timeline: v.strictObject({
|
|
204
|
+
data: query(),
|
|
205
|
+
class: opt(literal())
|
|
206
|
+
}),
|
|
207
|
+
Tree: v.strictObject({
|
|
208
|
+
data: query(),
|
|
209
|
+
class: opt(literal())
|
|
210
|
+
}),
|
|
211
|
+
Calendar: v.strictObject({
|
|
212
|
+
value: opt(literal()),
|
|
213
|
+
range: opt(literal()),
|
|
214
|
+
min: opt(literal()),
|
|
215
|
+
max: opt(literal()),
|
|
216
|
+
label: opt(literal()),
|
|
217
|
+
class: opt(literal())
|
|
218
|
+
}),
|
|
219
|
+
StreamText: v.strictObject({
|
|
220
|
+
streamSource: stream(),
|
|
221
|
+
autostart: opt(literal()),
|
|
222
|
+
class: opt(literal())
|
|
223
|
+
}),
|
|
224
|
+
ChatThread: v.strictObject({
|
|
225
|
+
roomChannel: room(),
|
|
226
|
+
send: opt(literal()),
|
|
227
|
+
receive: opt(literal()),
|
|
228
|
+
placeholder: opt(literal()),
|
|
229
|
+
sendLabel: opt(literal()),
|
|
230
|
+
emptyMessage: opt(literal()),
|
|
231
|
+
message: opt(snippet()),
|
|
232
|
+
class: opt(literal())
|
|
233
|
+
}),
|
|
234
|
+
Feed: v.strictObject({
|
|
235
|
+
streamSource: opt(stream()),
|
|
236
|
+
roomChannel: opt(room()),
|
|
237
|
+
receive: opt(literal()),
|
|
238
|
+
max: opt(literal()),
|
|
239
|
+
emptyMessage: opt(literal()),
|
|
240
|
+
item: opt(snippet()),
|
|
241
|
+
class: opt(literal())
|
|
242
|
+
}),
|
|
243
|
+
PresenceAvatars: v.strictObject({
|
|
244
|
+
roomChannel: room(),
|
|
245
|
+
max: opt(literal()),
|
|
246
|
+
size: opt(literal()),
|
|
247
|
+
label: opt(literal()),
|
|
248
|
+
showCount: opt(literal()),
|
|
249
|
+
class: opt(literal())
|
|
250
|
+
}),
|
|
251
|
+
LiveLog: v.strictObject({
|
|
252
|
+
streamSource: opt(stream()),
|
|
253
|
+
roomChannel: opt(room()),
|
|
254
|
+
receive: opt(literal()),
|
|
255
|
+
maxLines: opt(literal()),
|
|
256
|
+
class: opt(literal())
|
|
257
|
+
})
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Snippet slot signatures: tag → prop → ordered arg names the component
|
|
262
|
+
* passes when it renders the slot. The generator checks a bound Snippet
|
|
263
|
+
* unit's declared `args` against this signature (exact names, exact order),
|
|
264
|
+
* so a mismatch refuses at generate time.
|
|
265
|
+
*/
|
|
266
|
+
export const snippetSlots = {
|
|
267
|
+
Table: { cell: ['row', 'column', 'value'], empty: [] },
|
|
268
|
+
DataTable: { cell: ['row', 'column', 'value'], empty: [] },
|
|
269
|
+
Kanban: { card: ['card'], empty: [] },
|
|
270
|
+
ChatThread: { message: ['message'] },
|
|
271
|
+
Feed: { item: ['item'] }
|
|
272
|
+
};
|
package/src/index.js
CHANGED
|
@@ -83,6 +83,7 @@ export { default as DateRangePicker } from './components/DateRangePicker.n';
|
|
|
83
83
|
export { default as TimePicker } from './components/TimePicker.n';
|
|
84
84
|
export { default as Calendar } from './components/Calendar.n';
|
|
85
85
|
export { default as DataTable } from './components/DataTable.n';
|
|
86
|
+
export { default as Table } from './components/Table.n';
|
|
86
87
|
|
|
87
88
|
// 0.0.5 — CSS-only motion components
|
|
88
89
|
export { default as AvatarGroup } from './components/AvatarGroup.n';
|
|
@@ -98,3 +99,4 @@ export { cn } from './lib/cn.js';
|
|
|
98
99
|
export { variantClasses } from './lib/variants.js';
|
|
99
100
|
export { presetUI } from './auto-import.js';
|
|
100
101
|
export { toast, notify, dismiss, clear } from './lib/toast.svelte.js';
|
|
102
|
+
export { contracts } from './contracts.js';
|
|
@@ -12,3 +12,4 @@ export { focusTrap } from './focusTrap.svelte.js';
|
|
|
12
12
|
export { lock as scrollLockAcquire, unlock as scrollLockRelease, scrollLock } from './scrollLock.svelte.js';
|
|
13
13
|
export { useFloating, positionAt } from './floating.svelte.js';
|
|
14
14
|
export { rovingTabindex } from './rovingTabindex.svelte.js';
|
|
15
|
+
export { optimisticOverlay } from './optimistic.svelte.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimistic overlay: a client-side id → value map that shadows server truth
|
|
3
|
+
* until the next reload. A user gesture (drag-drop, toggle) lands instantly
|
|
4
|
+
* via `set`, while the bound action persists it in the background; `get`
|
|
5
|
+
* answers the overlaid value and falls back to the server's. Extracted from
|
|
6
|
+
* Kanban's drag-drop so other components can share the interaction model.
|
|
7
|
+
*/
|
|
8
|
+
export function optimisticOverlay() {
|
|
9
|
+
let overlay = $state({});
|
|
10
|
+
return {
|
|
11
|
+
get(id, base) {
|
|
12
|
+
return overlay[id] ?? base;
|
|
13
|
+
},
|
|
14
|
+
set(id, value) {
|
|
15
|
+
overlay = { ...overlay, [id]: value };
|
|
16
|
+
},
|
|
17
|
+
clear() {
|
|
18
|
+
overlay = {};
|
|
19
|
+
},
|
|
20
|
+
get size() {
|
|
21
|
+
return Object.keys(overlay).length;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|