@sylwellsoftware/fray 0.6.0 → 1.0.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/README.md +426 -205
- package/colors/README.md +26 -23
- package/colors/gray/colors.css +1 -42
- package/colors/green/colors.css +1 -42
- package/colors/iceblue/colors.css +5 -43
- package/colors/ocean/colors.css +1 -42
- package/colors/orange/colors.css +1 -42
- package/colors/purple/colors.css +1 -42
- package/colors/red/colors.css +1 -42
- package/colors/yellow/colors.css +1 -42
- package/dist/Components/Placeholder.d.ts +1 -2
- package/dist/Components/Placeholder.d.ts.map +1 -1
- package/dist/Components/component.d.ts +15 -7
- package/dist/Components/component.d.ts.map +1 -1
- package/dist/Components/data/descriptionList.d.ts +3 -1
- package/dist/Components/data/descriptionList.d.ts.map +1 -1
- package/dist/Components/data/listview/listview.d.ts +1 -2
- package/dist/Components/data/listview/listview.d.ts.map +1 -1
- package/dist/Components/data/table/DataTable.d.ts +1 -1
- package/dist/Components/data/table/DataTable.d.ts.map +1 -1
- package/dist/Components/data/table/FilterPanel.d.ts +2 -4
- package/dist/Components/data/table/FilterPanel.d.ts.map +1 -1
- package/dist/Components/data/table/TableHeader.d.ts +2 -0
- package/dist/Components/data/table/TableHeader.d.ts.map +1 -1
- package/dist/Components/data/table/TableHeaderCell.d.ts +1 -1
- package/dist/Components/data/table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Components/data/treeview/treeitem.d.ts +1 -0
- package/dist/Components/data/treeview/treeitem.d.ts.map +1 -1
- package/dist/Components/data/treeview/treeview.d.ts +2 -1
- package/dist/Components/data/treeview/treeview.d.ts.map +1 -1
- package/dist/Components/dialog/dialog.d.ts +6 -2
- package/dist/Components/dialog/dialog.d.ts.map +1 -1
- package/dist/Components/layout/header.d.ts +19 -0
- package/dist/Components/layout/header.d.ts.map +1 -0
- package/dist/Components/layout/panel.d.ts +7 -3
- package/dist/Components/layout/panel.d.ts.map +1 -1
- package/dist/Components/layout/sidebar.d.ts +4 -0
- package/dist/Components/layout/sidebar.d.ts.map +1 -1
- package/dist/Components/layout/splitView.d.ts +1 -1
- package/dist/Components/layout/splitView.d.ts.map +1 -1
- package/dist/Components/layout/tabpanel/tab.d.ts +3 -0
- package/dist/Components/layout/tabpanel/tab.d.ts.map +1 -1
- package/dist/Components/layout/tabpanel/tabline.d.ts +1 -2
- package/dist/Components/layout/tabpanel/tabline.d.ts.map +1 -1
- package/dist/Components/layout/tabpanel/tabpanel.d.ts +10 -2
- package/dist/Components/layout/tabpanel/tabpanel.d.ts.map +1 -1
- package/dist/Components/lineinputs/CheckableControl.d.ts +7 -0
- package/dist/Components/lineinputs/CheckableControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/LabeledInputControl.d.ts +7 -0
- package/dist/Components/lineinputs/LabeledInputControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/SelectControl.d.ts +7 -0
- package/dist/Components/lineinputs/SelectControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/checkbox/Checkbox.d.ts +11 -6
- package/dist/Components/lineinputs/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts +1 -2
- package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts.map +1 -1
- package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts +1 -2
- package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts.map +1 -1
- package/dist/Components/lineinputs/dropdown.d.ts +7 -6
- package/dist/Components/lineinputs/dropdown.d.ts.map +1 -1
- package/dist/Components/lineinputs/label.d.ts +14 -0
- package/dist/Components/lineinputs/label.d.ts.map +1 -0
- package/dist/Components/lineinputs/radio.d.ts +64 -0
- package/dist/Components/lineinputs/radio.d.ts.map +1 -0
- package/dist/Components/lineinputs/textbox.d.ts +7 -6
- package/dist/Components/lineinputs/textbox.d.ts.map +1 -1
- package/dist/Components/lineinputs/toggle.d.ts +10 -3
- package/dist/Components/lineinputs/toggle.d.ts.map +1 -1
- package/dist/Components/menu/button.d.ts +6 -3
- package/dist/Components/menu/button.d.ts.map +1 -1
- package/dist/Components/menu/toolbar.d.ts +1 -1
- package/dist/Components/menu/toolbar.d.ts.map +1 -1
- package/dist/Components/status/progressBar.d.ts +1 -1
- package/dist/Components/status/progressBar.d.ts.map +1 -1
- package/dist/Components/theme/stylesheetPicker.d.ts +6 -7
- package/dist/Components/theme/stylesheetPicker.d.ts.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3378 -1454
- package/dist/index.js.map +1 -1
- package/dist/jsx-dev-runtime-CRDpvXOH.js +1208 -0
- package/dist/jsx-dev-runtime-CRDpvXOH.js.map +1 -0
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/routing/RouteLink.d.ts +18 -0
- package/dist/routing/RouteLink.d.ts.map +1 -0
- package/dist/routing/RouteQuery.d.ts +19 -0
- package/dist/routing/RouteQuery.d.ts.map +1 -0
- package/dist/routing/RouteScope.d.ts +18 -0
- package/dist/routing/RouteScope.d.ts.map +1 -0
- package/dist/routing/RouteValue.d.ts +19 -0
- package/dist/routing/RouteValue.d.ts.map +1 -0
- package/dist/routing/navigationAdapter.d.ts +33 -0
- package/dist/routing/navigationAdapter.d.ts.map +1 -0
- package/dist/routing/route.d.ts +64 -0
- package/dist/routing/route.d.ts.map +1 -0
- package/dist/routing/router.d.ts +109 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/runtime.d.ts +10 -13
- package/dist/runtime.d.ts.map +1 -1
- package/dist/styling/styleRegistry.d.ts +0 -4
- package/dist/styling/styleRegistry.d.ts.map +1 -1
- package/dist/styling/theme.d.ts +14 -2
- package/dist/styling/theme.d.ts.map +1 -1
- package/package.json +5 -6
- package/styles/structural.css +942 -320
- package/themes/README.md +30 -72
- package/themes/base.css +311 -0
- package/themes/java/theme.css +14 -411
- package/themes/minimal/theme.css +3 -407
- package/themes/shiny/theme.css +127 -500
- package/dist/jsx-dev-runtime-CqunwZ8j.js +0 -1174
- package/dist/jsx-dev-runtime-CqunwZ8j.js.map +0 -1
- package/dist/styling/baseStyleDefinitions.d.ts +0 -75
- package/dist/styling/baseStyleDefinitions.d.ts.map +0 -1
- package/themes/dark.css +0 -86
- package/themes/light.css +0 -86
package/README.md
CHANGED
|
@@ -27,10 +27,12 @@ Presentation should be equally direct. Native HTML already defines buttons,
|
|
|
27
27
|
inputs, tables, lists, progress, dialogs, and landmarks, so Fray uses those
|
|
28
28
|
elements when their semantics match. Components that need another boundary use
|
|
29
29
|
readable light-DOM host names rather than framework identity classes.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
Within a component host, Fray uses classes for owned parts, purely visual
|
|
31
|
+
states with no semantic equivalent, and meaningful reusable traits; those
|
|
32
|
+
classes do not identify components. Native and ARIA state attributes remain the
|
|
33
|
+
sole semantic state source and are targeted directly by CSS. Renderer markers,
|
|
34
|
+
diagnostics, interoperability, and actual data use `data-*`; Fray does not use
|
|
35
|
+
`data-*` as its routine component-CSS hook.
|
|
34
36
|
|
|
35
37
|
## Design model
|
|
36
38
|
|
|
@@ -85,21 +87,24 @@ Responsibility stays at the narrowest layer that understands it:
|
|
|
85
87
|
| Domain state, service implementations/providers, endpoint configuration, page composition, theme availability and selection policy | Application |
|
|
86
88
|
| DOM structure, native events, accessibility, component lifetime, service-scope propagation, visual async states | Fray components/runtime |
|
|
87
89
|
| Mutable/computed values, query timing/results, fetch state, optional causality | Glue |
|
|
88
|
-
|
|
|
90
|
+
| Remote wire serialization and retrieval mechanism | Injected Glue query handler/application adapter |
|
|
91
|
+
| Browser navigation placement, restoration, and routed component integration | Caller-owned Fray router with an injected navigation adapter |
|
|
89
92
|
| Layout/flow CSS and stable component/part hooks | Fray structural styling |
|
|
90
93
|
| Look-and-feel treatment and palette | Separately loaded Fray-compatible theme/color CSS |
|
|
91
94
|
|
|
92
95
|
## Set up a browser application
|
|
93
96
|
|
|
94
|
-
Fray ships ESM, TypeScript declarations, automatic/classic JSX runtimes,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
Fray ships ESM, TypeScript declarations, automatic/classic JSX runtimes,
|
|
98
|
+
variable defaults, replaceable theme treatments, and replaceable color
|
|
99
|
+
palettes. Glue is a peer dependency. Applications collect structural CSS from
|
|
100
|
+
the component classes they actually declare.
|
|
97
101
|
|
|
98
102
|
```ts
|
|
99
103
|
import {Emitter} from '@sylwellsoftware/glue'
|
|
100
104
|
import {
|
|
101
105
|
Button,
|
|
102
106
|
Component,
|
|
107
|
+
Header,
|
|
103
108
|
Panel,
|
|
104
109
|
Sidebar,
|
|
105
110
|
Textbox,
|
|
@@ -107,7 +112,7 @@ import {
|
|
|
107
112
|
createFrayRuntime,
|
|
108
113
|
h,
|
|
109
114
|
} from '@sylwellsoftware/fray'
|
|
110
|
-
import '@sylwellsoftware/fray/
|
|
115
|
+
import '@sylwellsoftware/fray/themes/base.css'
|
|
111
116
|
import '@sylwellsoftware/fray/colors/iceblue/colors.css'
|
|
112
117
|
import '@sylwellsoftware/fray/themes/minimal/theme.css'
|
|
113
118
|
|
|
@@ -117,7 +122,10 @@ class App extends Component {
|
|
|
117
122
|
static dependencies = [Button, Panel, Textbox, Toolbar]
|
|
118
123
|
|
|
119
124
|
render() {
|
|
120
|
-
return h(Panel, {
|
|
125
|
+
return h(Panel, {
|
|
126
|
+
className: 'fray-fill-horizontal fray-fill-vertical',
|
|
127
|
+
header: 'Profile',
|
|
128
|
+
},
|
|
121
129
|
h(Textbox, {label: 'Name', valueEmitter: name}),
|
|
122
130
|
h(Toolbar, {label: 'Profile actions'},
|
|
123
131
|
h(Button, {label: 'Save', onClick: () => save(name.get())})))
|
|
@@ -125,6 +133,7 @@ class App extends Component {
|
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
const runtime = createFrayRuntime()
|
|
136
|
+
runtime.registerStyles(App).injectStyles(document)
|
|
128
137
|
runtime.mount(runtime.create(App), document.querySelector('#app')!)
|
|
129
138
|
|
|
130
139
|
function save(value: string) {
|
|
@@ -132,6 +141,14 @@ function save(value: string) {
|
|
|
132
141
|
}
|
|
133
142
|
```
|
|
134
143
|
|
|
144
|
+
Root sizing is application-owned. Add `fray-fill-horizontal` to a rendered
|
|
145
|
+
application root to claim exactly `100vw`, `fray-fill-vertical` to claim
|
|
146
|
+
exactly `100vh`, or both for a fullscreen application. Each axis supplies its
|
|
147
|
+
own root overflow fallback and zero minimum; descendant islands are bounded
|
|
148
|
+
and scroll on only the opted-in axes. Other components retain their intrinsic
|
|
149
|
+
minimums and their own structural overflow contracts. A root with neither
|
|
150
|
+
modifier keeps its ordinary embedded/content-sized behavior.
|
|
151
|
+
|
|
135
152
|
The prebuilt structural file targets Fray's default `fray-` hosts. Applications
|
|
136
153
|
with custom components or configured host names may instead register their root
|
|
137
154
|
dependencies and call `runtime.injectStyles(document)`; collection remains
|
|
@@ -214,6 +231,164 @@ query state comes from caller-owned endpoint results. Function components stay
|
|
|
214
231
|
presentation-oriented and receive rendered values or emitters from a nearby
|
|
215
232
|
lifecycle-owning class component.
|
|
216
233
|
|
|
234
|
+
## Hierarchical browser routing
|
|
235
|
+
|
|
236
|
+
Routing is optional and application-scoped. Route descriptors name immutable
|
|
237
|
+
relative segments; the mounted component hierarchy supplies their parentage.
|
|
238
|
+
This lets a `TabPanel` discover immediate routes from ordinary `Tab`
|
|
239
|
+
annotations without requiring a duplicate central route tree:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import {Emitter} from '@sylwellsoftware/glue'
|
|
243
|
+
import {
|
|
244
|
+
Component,
|
|
245
|
+
RouteLink,
|
|
246
|
+
RouteQuery,
|
|
247
|
+
RouteUnavailableError,
|
|
248
|
+
RouteValue,
|
|
249
|
+
Tab,
|
|
250
|
+
TabPanel,
|
|
251
|
+
createBrowserRouter,
|
|
252
|
+
createHashNavigation,
|
|
253
|
+
createFrayRuntime,
|
|
254
|
+
defineRoute,
|
|
255
|
+
defineRouteParameter,
|
|
256
|
+
routeTarget,
|
|
257
|
+
stringRouteCodec,
|
|
258
|
+
waitForRouteValue,
|
|
259
|
+
} from '@sylwellsoftware/fray'
|
|
260
|
+
|
|
261
|
+
const routes = {
|
|
262
|
+
changes: defineRoute('changes'),
|
|
263
|
+
security: defineRoute('security'),
|
|
264
|
+
overview: defineRoute('security-overview', 'overview'),
|
|
265
|
+
projects: defineRoute('security-projects', 'projects'),
|
|
266
|
+
}
|
|
267
|
+
const activeApplication = new Emitter('changes')
|
|
268
|
+
const activeSecurityView = new Emitter('overview')
|
|
269
|
+
|
|
270
|
+
class SecurityApplication extends Component {
|
|
271
|
+
render() {
|
|
272
|
+
return <TabPanel valueEmitter={activeSecurityView} label="Security views">
|
|
273
|
+
<Tab id="overview" label="Overview" route={routes.overview}>Summary</Tab>
|
|
274
|
+
<Tab id="projects" label="Projects" route={routes.projects}>Projects</Tab>
|
|
275
|
+
</TabPanel>
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static dependencies = [Tab, TabPanel]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class App extends Component {
|
|
282
|
+
render() {
|
|
283
|
+
return <>
|
|
284
|
+
<nav aria-label="Applications">
|
|
285
|
+
<RouteLink to={routeTarget(routes.security, routes.projects)}>
|
|
286
|
+
Security projects
|
|
287
|
+
</RouteLink>
|
|
288
|
+
</nav>
|
|
289
|
+
<TabPanel valueEmitter={activeApplication} label="Applications">
|
|
290
|
+
<Tab id="changes" label="Changes" route={routes.changes}>Changes</Tab>
|
|
291
|
+
<Tab id="security" label="Security" route={routes.security}>
|
|
292
|
+
<SecurityApplication />
|
|
293
|
+
</Tab>
|
|
294
|
+
</TabPanel>
|
|
295
|
+
</>
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
static dependencies = [RouteLink, SecurityApplication, Tab, TabPanel]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const router = createBrowserRouter({adapter: createHashNavigation(window)})
|
|
302
|
+
const runtime = createFrayRuntime({router})
|
|
303
|
+
const app = runtime.mount(runtime.create(App), document.querySelector('#app')!)
|
|
304
|
+
|
|
305
|
+
addEventListener('pagehide', () => {
|
|
306
|
+
app.destroy()
|
|
307
|
+
router.dispose()
|
|
308
|
+
}, {once: true})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The runtime opens the root scope. A selected routed tab opens its own scope for
|
|
312
|
+
nested components, so `/security/projects` restores the outer tab first and
|
|
313
|
+
then discovers and restores the inner tab. Every scope declares when its
|
|
314
|
+
immediate registrations are complete; this distinguishes an unknown child
|
|
315
|
+
from one whose parent has not mounted yet. Duplicate IDs or literal paths and
|
|
316
|
+
multiple parameter routes in one scope fail at completion. Literal routes take
|
|
317
|
+
precedence over the optional parameter route.
|
|
318
|
+
|
|
319
|
+
`RouteLink` is a native anchor. A descriptor resolves against the current
|
|
320
|
+
lineage, which is convenient for sibling destinations. A link outside the
|
|
321
|
+
destination's mounted lineage uses `routeTarget(...)` with the full chain from
|
|
322
|
+
the root. Modified clicks, downloads, and non-`_self` targets retain native
|
|
323
|
+
browser behavior. `router.navigate(target)` pushes by default;
|
|
324
|
+
`router.redirect(target)` and `navigate(target, {history: 'replace'})` replace.
|
|
325
|
+
`router.href(target)`, `router.resolve(descriptor, context)`, and
|
|
326
|
+
`router.isActive(target, exact)` support advanced composition.
|
|
327
|
+
|
|
328
|
+
Dynamic path values use a typed codec and an application-owned nullable
|
|
329
|
+
emitter:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
const projectRoute = defineRouteParameter('project', stringRouteCodec, 'project-id')
|
|
333
|
+
const selectedProjectId = new Emitter<string | null>(null)
|
|
334
|
+
|
|
335
|
+
<RouteValue
|
|
336
|
+
route={projectRoute}
|
|
337
|
+
valueEmitter={selectedProjectId}
|
|
338
|
+
resolve={async (projectId, _context, signal) => {
|
|
339
|
+
const projects = await waitForRouteValue(
|
|
340
|
+
projectResults,
|
|
341
|
+
(items) => items.length > 0,
|
|
342
|
+
signal,
|
|
343
|
+
)
|
|
344
|
+
if (!projects.some(({id}) => id === projectId)) {
|
|
345
|
+
throw new RouteUnavailableError(`Unknown project "${projectId}"`)
|
|
346
|
+
}
|
|
347
|
+
return projectId
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
<ProjectView />
|
|
351
|
+
</RouteValue>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Resolvers run in path order, may normalize the decoded value, and receive the
|
|
355
|
+
settled ancestor values plus an `AbortSignal`. A newer navigation or router
|
|
356
|
+
disposal aborts outstanding work. `waitForRouteValue` is a convenience for a
|
|
357
|
+
Glue readable; fetching, authorization, retries, and domain validation remain
|
|
358
|
+
application-owned. Set `scopeChildren` on `RouteValue` only when further route
|
|
359
|
+
levels live beneath the dynamic value. Leaving it off avoids remounting an
|
|
360
|
+
otherwise stable leaf view when selection changes.
|
|
361
|
+
|
|
362
|
+
Explicit query bindings make selected shareable view state routable without
|
|
363
|
+
coupling the originating control to the router:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<RouteQuery
|
|
367
|
+
name="range"
|
|
368
|
+
valueEmitter={historyRange}
|
|
369
|
+
codec={historyRangeCodec}
|
|
370
|
+
defaultValue="12m"
|
|
371
|
+
>
|
|
372
|
+
<HistoryView />
|
|
373
|
+
</RouteQuery>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Defaults are omitted, owned names sort deterministically, foreign query keys
|
|
377
|
+
are preserved, and passive emitter changes replace the current URL. Invalid
|
|
378
|
+
owned values reset to the default and produce a structured `router.issue`.
|
|
379
|
+
Path failures similarly fall back to the deepest resolved parent and replace
|
|
380
|
+
the failed entry. Applications observe `router.transition` for pending/idle
|
|
381
|
+
state and render `router.issue` as localized, accessible feedback. An explicit
|
|
382
|
+
navigation clears the issue.
|
|
383
|
+
|
|
384
|
+
Choose `createHashNavigation(window)` for static hosting; it reserves the URL
|
|
385
|
+
fragment. Choose `createHistoryNavigation(window, {basePath: '/app/'})` for
|
|
386
|
+
ordinary paths; the deployment must serve the application entry point for
|
|
387
|
+
direct requests below that base. `MemoryNavigationAdapter` supplies
|
|
388
|
+
deterministic tests and does not imply server rendering. The application owns
|
|
389
|
+
and disposes the router; destroying a runtime root removes mounted route
|
|
390
|
+
registrations but does not dispose the caller-owned router.
|
|
391
|
+
|
|
217
392
|
## Component host elements
|
|
218
393
|
|
|
219
394
|
Fray components with a wrapper render a standards-valid custom host element,
|
|
@@ -221,11 +396,11 @@ not a framework identity class. The default application runtime therefore
|
|
|
221
396
|
produces DOM such as:
|
|
222
397
|
|
|
223
398
|
```html
|
|
224
|
-
<fray-panel class="
|
|
225
|
-
<fray-textbox data-fray-component="textbox">
|
|
226
|
-
<input type="text">
|
|
399
|
+
<fray-panel data-fray class="island" data-fray-component="panel">
|
|
400
|
+
<fray-textbox data-fray data-fray-component="textbox">
|
|
401
|
+
<input data-fray type="text">
|
|
227
402
|
</fray-textbox>
|
|
228
|
-
<button data-fray-component="button">Save</button>
|
|
403
|
+
<button data-fray data-fray-component="button">Save</button>
|
|
229
404
|
</fray-panel>
|
|
230
405
|
```
|
|
231
406
|
|
|
@@ -234,48 +409,38 @@ Native semantics remain native: `Button` renders `button`, `Toggle` renders
|
|
|
234
409
|
`thead`/`th`. `Tab` is a declarative child consumed by `TabPanel` and has no
|
|
235
410
|
independent root. The
|
|
236
411
|
`data-fray-component` keeps diagnostics unambiguous. It is not a structural or
|
|
237
|
-
theme selector. Fray may merge public presentation traits such as `
|
|
412
|
+
theme selector. Fray may merge public presentation traits such as `island`
|
|
238
413
|
with an application-supplied `class`/`className`; those traits describe a
|
|
239
414
|
reusable capability, not component identity.
|
|
240
415
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
HTML custom-element names must contain a hyphen, so prefixless mode uses each
|
|
267
|
-
component's standards-valid standalone name rather than invalid names such as
|
|
268
|
-
`<panel>` or `<listview>`. Prefixes and overrides must be lowercase kebab-case;
|
|
269
|
-
an exact override must itself be a non-reserved custom-element name.
|
|
416
|
+
Pass `island` to a fixed-host component when that surface should be visually
|
|
417
|
+
separated from the page. Fray adds the reusable `island` class to its host;
|
|
418
|
+
applications may use the same class on deliberate native surface boundaries.
|
|
419
|
+
The component-owned rule consumes `--island-*` variables, so an island theme
|
|
420
|
+
can add gutters, an edge, and elevation while a flat theme can leave the
|
|
421
|
+
modifier visually neutral. Islands are one surface layer: nesting an `island`
|
|
422
|
+
component below another island is rejected. Application-authored native island
|
|
423
|
+
classes must follow the same no-nesting invariant. A surface island never
|
|
424
|
+
creates a nested theme or palette scope.
|
|
425
|
+
|
|
426
|
+
Built-in host names are fixed public DOM: Fray adds its one required custom
|
|
427
|
+
element hyphen and removes internal word separators from the component stem.
|
|
428
|
+
For example, `Panel`, `ListView`, and `ThemePicker` render as `<fray-panel>`,
|
|
429
|
+
`<fray-listview>`, and `<fray-themepicker>`. They cannot be prefixed, renamed,
|
|
430
|
+
or made prefix-free at runtime. This lets distributed stylesheets target hosts
|
|
431
|
+
directly and predictably.
|
|
432
|
+
|
|
433
|
+
Every element created by Fray's renderer also receives the boolean `data-fray`
|
|
434
|
+
attribute. Themes can therefore target native Fray output without affecting
|
|
435
|
+
other UI libraries: `button[data-fray]`, `input[data-fray]`, and
|
|
436
|
+
`dialog[data-fray]`. The marker is renderer-owned and cannot be removed through
|
|
437
|
+
component props. `data-fray-component` remains diagnostic metadata, not an
|
|
438
|
+
ordinary styling selector.
|
|
270
439
|
|
|
271
|
-
Each runtime owns its element-name mapping and structural-style registry.
|
|
272
440
|
Create the runtime once at application startup, then create and mount the root
|
|
273
|
-
through that runtime.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Styles still live in the document's global cascade because these hosts are
|
|
277
|
-
deliberately unregistered light-DOM elements, not Web Components or Shadow DOM
|
|
278
|
-
boundaries.
|
|
441
|
+
through that runtime. Styles live in the document's global cascade because the
|
|
442
|
+
hosts are deliberately unregistered light-DOM elements, not Web Components or
|
|
443
|
+
Shadow DOM boundaries.
|
|
279
444
|
|
|
280
445
|
## Reactive templates
|
|
281
446
|
|
|
@@ -290,8 +455,8 @@ canonical vnode operation, but it is not the built-in component authoring
|
|
|
290
455
|
format. The workspace lint gate rejects new `h()` templates under
|
|
291
456
|
`packages/fray/src/Components`.
|
|
292
457
|
|
|
293
|
-
|
|
294
|
-
|
|
458
|
+
Custom hosts are available inside a TSX component through its protected `Host`
|
|
459
|
+
template component:
|
|
295
460
|
|
|
296
461
|
```tsx
|
|
297
462
|
interface BadgeProps extends ComponentProps {
|
|
@@ -307,12 +472,11 @@ class Badge extends Component<BadgeProps> {
|
|
|
307
472
|
}
|
|
308
473
|
|
|
309
474
|
static override hostName = 'badge'
|
|
310
|
-
static override standaloneHostName = 'ui-badge'
|
|
311
475
|
}
|
|
312
476
|
```
|
|
313
477
|
|
|
314
|
-
At runtime that template produces `<fray-badge
|
|
315
|
-
|
|
478
|
+
At runtime that template produces `<fray-badge>`. `hostName` is a stable
|
|
479
|
+
lowercase kebab-case component identity; Fray derives the fixed host spelling.
|
|
316
480
|
Native-root components such as `Button` use their native tag directly in TSX
|
|
317
481
|
instead of `Host`.
|
|
318
482
|
|
|
@@ -322,7 +486,7 @@ Glue values have explicit behavior at each template boundary:
|
|
|
322
486
|
| --- | --- |
|
|
323
487
|
| `{emitter}` | Render the current value and patch only that child range on emission. |
|
|
324
488
|
| `<Child source={emitter} />` | Pass the emitter object unchanged; the child owns how it consumes it. |
|
|
325
|
-
| `prop={live(emitter)}` | Subscribe a
|
|
489
|
+
| `prop={live(emitter)}` | Subscribe a DOM property or a component-declared live prop one way to the emitter's current value. |
|
|
326
490
|
| `<input bind:value={emitter} />` | Bind a writable string emitter and native `value` two ways. |
|
|
327
491
|
| `<input bind:checked={emitter} />` | Bind a writable boolean emitter and native `checked` two ways. |
|
|
328
492
|
| `this.read(emitter)` | Read during `render()` and rerender the component while that dependency is used. |
|
|
@@ -335,6 +499,16 @@ The component still owns and disposes emitters it creates. Direct rendering
|
|
|
335
499
|
uses only an emitter's value; use `snapshot()` when loading and error state
|
|
336
500
|
must affect the markup.
|
|
337
501
|
|
|
502
|
+
Component props do not implicitly unwrap emitters. A raw emitter prop passes
|
|
503
|
+
the emitter object to the component, while `live(emitter)` passes its current
|
|
504
|
+
value and subscribes at the renderer boundary. Every class component has an
|
|
505
|
+
explicit live-prop contract and rejects `live()` outside its allowlist in both
|
|
506
|
+
typed templates and at runtime. `live()` is reserved for small render-time
|
|
507
|
+
state such as availability, validation, and busy/pressed state. Identity,
|
|
508
|
+
callbacks, initial values, structural collections, and emitter ownership are
|
|
509
|
+
ordinary props. A data prop documented as accepting a readable emitter is a
|
|
510
|
+
separate input-source contract, not a `live()` prop.
|
|
511
|
+
|
|
338
512
|
This complete example uses mutable and derived emitters, direct emitter
|
|
339
513
|
children, raw emitter props, native two-way bindings, a one-way live property,
|
|
340
514
|
conditional tracked state, and a `LiveQuery` passed to a child:
|
|
@@ -460,7 +634,7 @@ class ChangeApp extends Component {
|
|
|
460
634
|
static dependencies = [Button, Panel, Preview, Results, Textbox]
|
|
461
635
|
}
|
|
462
636
|
|
|
463
|
-
const runtime = createFrayRuntime(
|
|
637
|
+
const runtime = createFrayRuntime()
|
|
464
638
|
runtime.registerStyles(ChangeApp).injectStyles(document)
|
|
465
639
|
runtime.mount(runtime.create(ChangeApp), document.querySelector('#app')!)
|
|
466
640
|
```
|
|
@@ -531,8 +705,10 @@ or `defaultValue` in new code.
|
|
|
531
705
|
|
|
532
706
|
## Stable component reference
|
|
533
707
|
|
|
534
|
-
All components also accept `children`, `className` (`class` is an alias),
|
|
535
|
-
sibling-local `key` through the common
|
|
708
|
+
All components also accept `children`, `className` (`class` is an alias), an
|
|
709
|
+
`island` surface modifier, and a sibling-local `key` through the common
|
|
710
|
+
component props. The modifier is static presentation input, not a `live()`
|
|
711
|
+
property.
|
|
536
712
|
|
|
537
713
|
| Component | Important props | User callback | State/emitter behavior |
|
|
538
714
|
| --- | --- | --- | --- |
|
|
@@ -540,20 +716,54 @@ sibling-local `key` through the common component props.
|
|
|
540
716
|
| `Toolbar` | `label`, `orientation`, `id`, `children` | None | Stateless named toolbar; orientation is horizontal or vertical. |
|
|
541
717
|
| `Textbox` | `label` or `ariaLabel`, value props, `disabled`, `required`, `readOnly`, `error`, native text constraints, `inputRef` | `onInput(value, event)`, `onChange(value, event)` | String `valueEmitter`; external emitter changes patch the native input without replacing it. |
|
|
542
718
|
| `Dropdown<T>` | `options`, `label` or `ariaLabel`, value props, `disabled`, `required`, `error`, `placeholder`, `name` | `onChange(value, event)` | Typed string/number `valueEmitter`; `options` may be an array or readable emitter. |
|
|
543
|
-
| `
|
|
544
|
-
| `
|
|
719
|
+
| `RadioButton` | `label`, `name`, `value`, `checked`, `disabled`, `required`, `error` | `onChange(checked, event)` | Native radio input with a labelled fixed shell; `checked`, `disabled`, `required`, and `error` support `live()`. |
|
|
720
|
+
| `RadioGroup<T>` | Plain-array `options`, `label` or `ariaLabel`, value props, `name`, `disabled`, `required`, `error` | `onChange(value, event)` | Native radio inputs with one selected `valueEmitter`; `disabled`, `required`, and `error` support `live()`. |
|
|
721
|
+
| `Toggle<T>` | `options`, `label` or `ariaLabel`, value props, `disabled`, `required`, `error` | `onChange(value, event)` | One selected value; `disabled`, `required`, and `error` support `live()`. |
|
|
722
|
+
| `Checkbox<T>` | `symbols`, `label`, value props, `disabled`, `required`, `error`, `name` | `onChange(value, event)` | Two-state semantic value by default; `disabled`, `required`, and `error` support `live()`. |
|
|
545
723
|
| `TriCheckbox` | Checkbox props except `symbols` | `onChange(value, event)` | Cycles deny → neutral → prefer using `FilterMode`. |
|
|
546
724
|
| `QuadCheckbox` | Checkbox props except `symbols` | `onChange(value, event)` | Cycles deny → neutral → prefer → require using `FilterMode`. |
|
|
547
|
-
| `
|
|
548
|
-
| `
|
|
549
|
-
| `
|
|
725
|
+
| `Header` | `id`, `headingId`, `level`, `children` | None | Styled heading surface using a native `h1`–`h6`; level defaults to `2`. |
|
|
726
|
+
| `Panel` | `header`, `toolbar`, `orientation`, `disabled`, `id`, `children` | None | Stateless labelled section that composes Header when header content exists; `disabled` describes the region but does not mutate descendant controls. |
|
|
727
|
+
| `Sidebar` | `header`, `toolbar`, `ariaLabel`, `id`, `children` | None | Fixed `fray-sidebar` host containing a native complementary region with fixed header/toolbar parts and independently scrolling content. |
|
|
728
|
+
| `SplitView` | `primary`, `secondary`, `direction`, `primarySize`, pane labels | None | Stateless, non-resizable two-pane flex layout with explicit overflow ownership and keyboard-focusable panes; `primarySize` is a flex-basis value. |
|
|
550
729
|
| `DescriptionList` / `DescriptionItem` | list `label`; item `term`, `value` or children | None | Native `dl`/`dt`/`dd` record summary with responsive term/value wrapping. |
|
|
551
|
-
| `ProgressBar` | `label`, `value` or `valueEmitter`, `max`, `valueText` | None |
|
|
730
|
+
| `ProgressBar` | `label`, `value` or `valueEmitter`, `max`, `valueText` | None | Native progress semantics with a clipped-label visual surface; a null value is indeterminate. |
|
|
552
731
|
| `ThemePicker` | `label` or `ariaLabel`, theme `options`, value props, `targetDocument`, `disabled` | `onChange(value, option, event)` | String `valueEmitter`; replaces only the theme stylesheet link. |
|
|
553
732
|
| `ColorPicker` | `label` or `ariaLabel`, color `options`, value props, `targetDocument`, `disabled` | `onChange(value, option, event)` | String `valueEmitter`; replaces only the color stylesheet link. |
|
|
554
|
-
| `Tab` | `id`, `label`, `disabled`, `children` | None | Declarative content marker consumed by `TabPanel`;
|
|
733
|
+
| `Tab` | `id`, `label`, `disabled`, optional literal `route`, `children` | None | Declarative content marker consumed by `TabPanel`; a route annotation binds tab activation to the current route scope. |
|
|
555
734
|
| `TabLine` | `tabs`, `label`, `baseId`, value props | `onChange(id, event)` | Active-tab `valueEmitter`; arrow keys skip disabled tabs, with Home/End support. |
|
|
556
|
-
| `TabPanel` | `tabs` or `Tab` children, `label`, `id`, value props | `onChange(id, event)` | Owns or consumes the active-tab emitter
|
|
735
|
+
| `TabPanel` | `tabs` or `Tab` children, `label`, `id`, value props | `onChange(id, event)` | Owns or consumes the active-tab emitter, wires the selected tabpanel, and contextually registers annotated tabs when a router is present. |
|
|
736
|
+
|
|
737
|
+
### Live binding and data-source contracts
|
|
738
|
+
|
|
739
|
+
Use `live()` only for the props listed here. All other component props reject
|
|
740
|
+
`live()` in TSX, `h()`, and at runtime. Form-control errors render an alert,
|
|
741
|
+
set invalid state, and associate the control or group with that message.
|
|
742
|
+
|
|
743
|
+
| Component | `live()` props | Dedicated reactive input/state props |
|
|
744
|
+
| --- | --- | --- |
|
|
745
|
+
| `Button` | `disabled`, `pressed`, `busy` | None |
|
|
746
|
+
| `Textbox` | `disabled`, `required`, `readOnly`, `error` | `valueEmitter` |
|
|
747
|
+
| `Dropdown` | `disabled`, `required`, `error` | `options` may be an array or readable emitter; `valueEmitter` |
|
|
748
|
+
| `RadioButton` | `checked`, `disabled`, `required`, `error` | None |
|
|
749
|
+
| `RadioGroup` | `disabled`, `required`, `error` | `valueEmitter`; `options` is always an ordinary array |
|
|
750
|
+
| `Toggle` | `disabled`, `required`, `error` | `valueEmitter`; `options` is always an ordinary array |
|
|
751
|
+
| `Checkbox`, `TriCheckbox`, `QuadCheckbox` | `disabled`, `required`, `error` | `valueEmitter` |
|
|
752
|
+
| `ThemePicker`, `ColorPicker` | `disabled` | `valueEmitter` |
|
|
753
|
+
| `Dialog` | `showCloseButton` | `valueEmitter` controls open state |
|
|
754
|
+
| `Panel` | `disabled` | None |
|
|
755
|
+
| `ProgressBar` | None | `valueEmitter` |
|
|
756
|
+
| `ListView` | None | `items` may be an array or readable emitter; selection emitters are outputs |
|
|
757
|
+
| `TreeView` | None | `nodes` may be an array or readable emitter; selected/expanded emitters are outputs |
|
|
758
|
+
| `DataTable` | None | `data` may be an array or readable emitter; `dataSource`/`rest` and selection emitters are explicit source/state contracts |
|
|
759
|
+
| `FilterPanel` | None | `options` may be an array or readable emitter |
|
|
760
|
+
| Layout, tab, description, toolbar, placeholder, and routing components | None | Their ordinary structural/configuration props require an owner rerender when changed |
|
|
761
|
+
|
|
762
|
+
Readable data sources carry their own fetch state and error through Glue's
|
|
763
|
+
`getFetchState()` and `getError()` APIs. `ListView`, `DataTable`, `FilterPanel`,
|
|
764
|
+
and `TreeView` surface those source failures as data-loading errors. This is
|
|
765
|
+
separate from a control's `error` prop, which represents validation or other
|
|
766
|
+
application-level input feedback.
|
|
557
767
|
|
|
558
768
|
Invalid option arrays, duplicate tab IDs, unsupported orientations, malformed
|
|
559
769
|
emitters, and non-function callbacks fail with descriptive errors.
|
|
@@ -609,6 +819,80 @@ h(Toggle, {
|
|
|
609
819
|
})
|
|
610
820
|
```
|
|
611
821
|
|
|
822
|
+
### RadioGroup
|
|
823
|
+
|
|
824
|
+
`RadioGroup` deliberately has a narrow reactive contract. Its selectable
|
|
825
|
+
`options` are ordinary structural input, its `valueEmitter` is a stable raw
|
|
826
|
+
writable state channel, and only `disabled`, `required`, and `error` accept one-way
|
|
827
|
+
`live()` bindings.
|
|
828
|
+
|
|
829
|
+
| Prop | Role | `live()` support |
|
|
830
|
+
| --- | --- | --- |
|
|
831
|
+
| `options` | Selectable value/label tuples supplied by the owner | No |
|
|
832
|
+
| `disabled` | Current availability state | Yes, with a readable boolean emitter |
|
|
833
|
+
| `required` | Current form-requirement state | Yes, with a readable boolean emitter |
|
|
834
|
+
| `error` | Current validation message and invalid state | Yes, with a readable error emitter |
|
|
835
|
+
| `valueEmitter` | Stable writable selected-value channel | Pass the emitter raw; do not wrap it |
|
|
836
|
+
| `value`, `defaultValue`, `initialValue` | Initial value when no `valueEmitter` is supplied | No |
|
|
837
|
+
| `id`, `label`, `ariaLabel`, `name` | Ordinary identity and presentation input | No `live()` binding |
|
|
838
|
+
| `onChange` | Action callback | No |
|
|
839
|
+
|
|
840
|
+
Static and live availability state can be combined without changing the option
|
|
841
|
+
contract:
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
const selectedView = new Emitter<'list' | 'grid'>('list')
|
|
845
|
+
const unavailable = new Emitter(false)
|
|
846
|
+
const mustChoose = new Emitter(true)
|
|
847
|
+
|
|
848
|
+
<RadioGroup
|
|
849
|
+
label="View"
|
|
850
|
+
options={[
|
|
851
|
+
['list', 'List'],
|
|
852
|
+
['grid', 'Grid'],
|
|
853
|
+
]}
|
|
854
|
+
valueEmitter={selectedView}
|
|
855
|
+
disabled={live(unavailable)}
|
|
856
|
+
required={live(mustChoose)}
|
|
857
|
+
/>
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
`live(optionsEmitter)` and a raw options emitter are both unsupported. When an
|
|
861
|
+
application genuinely owns a changing option vocabulary, its owning class
|
|
862
|
+
component must make that structural rerender explicit:
|
|
863
|
+
|
|
864
|
+
```tsx
|
|
865
|
+
interface ViewChooserProps extends ComponentProps {
|
|
866
|
+
options: ReadableEmitter<readonly RadioOption[]>
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
class ViewChooser extends Component<ViewChooserProps> {
|
|
870
|
+
render() {
|
|
871
|
+
return <RadioGroup
|
|
872
|
+
label="View"
|
|
873
|
+
options={this.read(this.props.options)}
|
|
874
|
+
/>
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
Calling `optionsEmitter.get()` directly in `render()` only reads a snapshot and
|
|
880
|
+
does not subscribe. `this.read(optionsEmitter)` rerenders the owner when the
|
|
881
|
+
array changes; normal vnode reconciliation then supplies the new ordinary
|
|
882
|
+
array prop to `RadioGroup`. The owner remains responsible for deciding what an
|
|
883
|
+
option removal means for its selected-value emitter. Fray does not silently
|
|
884
|
+
select, clear, or otherwise rewrite that state.
|
|
885
|
+
|
|
886
|
+
`label` is a `FrayChild`, so it may still contain an emitter that is rendered
|
|
887
|
+
and subscribed as child content. That fine-grained child behavior is distinct
|
|
888
|
+
from making the `label` property itself a `live()` binding. The same is true of
|
|
889
|
+
an individual `RadioOption` label: its rendered content may be reactive without
|
|
890
|
+
making the option array or its selectable values live.
|
|
891
|
+
|
|
892
|
+
Because `live()` subscriptions belong to a parent render record, pass live
|
|
893
|
+
props through JSX or `h()`. Direct `new RadioGroup(...)` construction accepts
|
|
894
|
+
resolved booleans and ordinary arrays only.
|
|
895
|
+
|
|
612
896
|
### Checkbox variants
|
|
613
897
|
|
|
614
898
|
```ts
|
|
@@ -620,6 +904,18 @@ h(QuadCheckbox, {label: 'Required tags', defaultValue: FilterMode.Require})
|
|
|
620
904
|
`FilterMode` values are `Deny`, `Neutral`, `Prefer`, and `Require`. The basic
|
|
621
905
|
checkbox uses neutral/prefer, while the variants expose the additional states.
|
|
622
906
|
|
|
907
|
+
### Header
|
|
908
|
+
|
|
909
|
+
```tsx
|
|
910
|
+
<Header id="portfolio-header" headingId="portfolio-title" level={2}>
|
|
911
|
+
Portfolio summary
|
|
912
|
+
</Header>
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
`level` is an integer from `1` through `6` and defaults to `2`. Header children
|
|
916
|
+
must be valid native heading content. The custom host owns presentation while
|
|
917
|
+
the nested native heading owns document semantics.
|
|
918
|
+
|
|
623
919
|
### Panel
|
|
624
920
|
|
|
625
921
|
```ts
|
|
@@ -642,10 +938,10 @@ h(Sidebar, {
|
|
|
642
938
|
}, h('ul', null, h('li', null, 'Release automation')))
|
|
643
939
|
```
|
|
644
940
|
|
|
645
|
-
The surrounding grid or flex layout must bound the Sidebar's height. Its
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
`header`.
|
|
941
|
+
The surrounding grid or flex layout must bound the Sidebar's height. Its native
|
|
942
|
+
complementary region is inside the fixed Sidebar host; Header and toolbar remain
|
|
943
|
+
fixed while the dedicated content part owns vertical scrolling and is keyboard-
|
|
944
|
+
focusable. Supply `ariaLabel` when there is no visible `header`.
|
|
649
945
|
|
|
650
946
|
### Declarative tabs
|
|
651
947
|
|
|
@@ -676,149 +972,74 @@ content and IDs.
|
|
|
676
972
|
|
|
677
973
|
## Styling and accessibility
|
|
678
974
|
|
|
679
|
-
Fray
|
|
975
|
+
A Fray application loads four ordered styling inputs:
|
|
976
|
+
|
|
977
|
+
| Order | Input | Responsibility |
|
|
978
|
+
| --- | --- | --- |
|
|
979
|
+
| 1 | `themes/base.css` | Default palette anchors, palette-ramp derivation, and semantic custom-property defaults only |
|
|
980
|
+
| 2 | Runtime-collected component CSS | Rules from the `static css` declarations of the component classes the application declares |
|
|
981
|
+
| 3 | `colors/<name>/colors.css` | Palette anchors and endpoints only |
|
|
982
|
+
| 4 | `themes/<name>/theme.css` | Intentional custom-property overrides; `color-scheme` is the only ordinary declaration allowed |
|
|
983
|
+
|
|
984
|
+
Application layout remains in application-owned CSS and is not a fifth Fray
|
|
985
|
+
layer. Named color and theme files never import the base file; load order is
|
|
986
|
+
explicit at the application entry point.
|
|
987
|
+
|
|
988
|
+
Component classes own every selector and every declaration that consumes a
|
|
989
|
+
Fray variable. Shared rules live in meaningful abstract component classes when
|
|
990
|
+
those components share DOM or behavior. For example, `Dropdown` inherits the
|
|
991
|
+
labeled-input contract and then the select-shell contract. The collector walks
|
|
992
|
+
that class chain base-to-derived, resolves each class's own `&` selectors
|
|
993
|
+
against the concrete host, and then traverses dependencies declared at every
|
|
994
|
+
level. The older named `baseStyles` recipe system has been removed.
|
|
995
|
+
|
|
996
|
+
A component's `static css` should contain the absolute minimum needed for its
|
|
997
|
+
contract. Every selector, declaration, pseudo-element, and variable must have a
|
|
998
|
+
specific purpose that cannot be expressed more simply. A base-class edit must
|
|
999
|
+
be reviewed against all descendants because its CSS is compiled into each
|
|
1000
|
+
concrete descendant that is used.
|
|
1001
|
+
|
|
1002
|
+
The checked-in `styles/structural.css` is a complete generated artifact for
|
|
1003
|
+
non-tree-shaken/diagnostic use. Normal applications should register their root
|
|
1004
|
+
component and inject the resulting usage-specific stylesheet:
|
|
680
1005
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
| Colors | `colors/<name>/colors.css` | Primary, secondary, and neutral ramps plus contrast/color primitives; no UI-semantic roles | Loaded separately and independently replaceable |
|
|
686
|
-
|
|
687
|
-
Component authors place only structure and mechanics in `static css`: display,
|
|
688
|
-
flow, sizing, positioning, overflow, stable state hooks, and consumption of
|
|
689
|
-
semantic variables. Reusable `static baseStyles` mappings apply named
|
|
690
|
-
structural rules to component selectors, while `static dependencies` let the
|
|
691
|
-
collector traverse a complete application tree, deduplicate definitions, and
|
|
692
|
-
generate one artifact. Literal palettes and treatment-specific shadows,
|
|
693
|
-
gradients, radii, and decoration do not belong in component CSS.
|
|
694
|
-
|
|
695
|
-
The initial supported treatments are `shiny`, `java`, and `minimal`. The color
|
|
696
|
-
catalog contains `iceblue`, `ocean`, `green`, `gray`, `orange`, `purple`, `red`,
|
|
697
|
-
and `yellow`. The older top-level `themes/light.css` and `themes/dark.css`
|
|
698
|
-
remain compatibility bundles; new applications should use the separated
|
|
699
|
-
contract.
|
|
700
|
-
|
|
701
|
-
These three treatments adapt the useful intent of earlier styling experiments
|
|
702
|
-
rather than preserving their CSS literally. Application-specific selectors,
|
|
703
|
-
duplicated declarations, and mixed structural/presentation rules were removed;
|
|
704
|
-
the characteristic restrained Minimal, classic raised Java, and layered glossy
|
|
705
|
-
Shiny treatments were rebuilt on the current component hooks and variables.
|
|
706
|
-
|
|
707
|
-
### Hierarchical custom properties
|
|
708
|
-
|
|
709
|
-
CSS custom properties are Fray's primary theme integration protocol:
|
|
710
|
-
|
|
711
|
-
```text
|
|
712
|
-
colors.css
|
|
713
|
-
--palette-primary-* / --palette-secondary-* / --palette-neutral-*
|
|
714
|
-
│
|
|
715
|
-
▼
|
|
716
|
-
theme.css
|
|
717
|
-
global UI roles (font, spacing, shape, surface)
|
|
718
|
-
│
|
|
719
|
-
▼
|
|
720
|
-
generic families (header, button, input, panel, selection)
|
|
721
|
-
│
|
|
722
|
-
▼
|
|
723
|
-
optional variants (table header, tab button, toggle button,
|
|
724
|
-
dropdown trigger, dialog header)
|
|
725
|
-
│
|
|
726
|
-
▼
|
|
727
|
-
structural CSS and custom components
|
|
1006
|
+
```ts
|
|
1007
|
+
const runtime = createFrayRuntime()
|
|
1008
|
+
runtime.registerStyles(App).injectStyles(document)
|
|
1009
|
+
runtime.mount(runtime.create(App), root)
|
|
728
1010
|
```
|
|
729
1011
|
|
|
730
|
-
|
|
731
|
-
toward its generic family. A theme can therefore change all header-like or
|
|
732
|
-
button-like elements with a few assignments, then override only the variants
|
|
733
|
-
that should look different. `frayThemeVariableCatalog` exports this contract in
|
|
734
|
-
machine-readable form, including every variable's layer, family, value kind,
|
|
735
|
-
purpose, and optional fallback.
|
|
1012
|
+
### Custom properties
|
|
736
1013
|
|
|
737
|
-
|
|
738
|
-
|
|
1014
|
+
`base.css` derives full primary, secondary, and neutral ramps from each
|
|
1015
|
+
palette's `500` anchor plus light/dark mix endpoints. A color file therefore
|
|
1016
|
+
sets anchors and endpoints, while a theme maps or overrides semantic families
|
|
1017
|
+
such as `--button-*`, `--input-*`, `--panel-*`, and `--selection-*`.
|
|
1018
|
+
`frayThemeVariableCatalog` exposes the supported vocabulary.
|
|
739
1019
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
background: var(--table-header-background, var(--header-background));
|
|
744
|
-
}
|
|
745
|
-
```
|
|
1020
|
+
`--application-background` owns the canvas behind an axis-filling application
|
|
1021
|
+
root and its islands. Its base value is the white palette endpoint, which
|
|
1022
|
+
Shiny and Minimal both retain.
|
|
746
1023
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
introduced solely to carry a split trait.
|
|
754
|
-
|
|
755
|
-
Themes directly target native elements, public traits, native pseudo-parts,
|
|
756
|
-
and native/ARIA state. They never target `data-fray-component` or `data-part`.
|
|
757
|
-
Every theme seeds inherited variables with a zero-specificity
|
|
758
|
-
`:root`/`[data-theme]` boundary rule. Presentation selectors use `@scope` with
|
|
759
|
-
nested theme roots and `[data-theme-exclude]` limits, a named cascade layer,
|
|
760
|
-
and low-specificity `:where()` selectors. Shiny's highlights and
|
|
761
|
-
select/progress decoration follow that contract and yield to native
|
|
762
|
-
representation under forced colors.
|
|
763
|
-
|
|
764
|
-
`coloredlike` defaults to the primary palette. Components can provide
|
|
765
|
-
`--colored-base`, `--colored-light`, `--colored-dark`, and
|
|
766
|
-
`--colored-contrast`; the active theme decides whether those inputs become a
|
|
767
|
-
flat color, gradient, other polish, or no special treatment.
|
|
1024
|
+
The explicit `island` modifier consumes `--island-margin`,
|
|
1025
|
+
`--island-padding`, `--island-background`, `--island-border`,
|
|
1026
|
+
`--island-radius`, and `--island-shadow`. Base and Minimal keep its layout and
|
|
1027
|
+
elevation neutral; Shiny uses it for Bank2-style separated surfaces. Themes do
|
|
1028
|
+
not infer island boundaries from component type or nesting, and islands cannot
|
|
1029
|
+
contain other islands.
|
|
768
1030
|
|
|
769
1031
|
### Runtime selection
|
|
770
1032
|
|
|
771
1033
|
`replaceFrayStylesheet` maintains one
|
|
772
1034
|
`link[data-fray-stylesheet="theme"]` and one
|
|
773
1035
|
`link[data-fray-stylesheet="colors"]`. Replacing either link also sets the
|
|
774
|
-
corresponding
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
The default option catalogs resolve URLs against Fray's published package
|
|
779
|
-
layout for direct ESM/CDN use. A bundled application should ask its bundler to
|
|
780
|
-
emit each selectable CSS file as an asset and supply those resulting URLs:
|
|
781
|
-
|
|
782
|
-
```tsx
|
|
783
|
-
import {ColorPicker, Component, ThemePicker} from '@sylwellsoftware/fray'
|
|
784
|
-
import iceblueHref from '@sylwellsoftware/fray/colors/iceblue/colors.css?url'
|
|
785
|
-
import purpleHref from '@sylwellsoftware/fray/colors/purple/colors.css?url'
|
|
786
|
-
import minimalHref from '@sylwellsoftware/fray/themes/minimal/theme.css?url'
|
|
787
|
-
import shinyHref from '@sylwellsoftware/fray/themes/shiny/theme.css?url'
|
|
788
|
-
|
|
789
|
-
const themes = [
|
|
790
|
-
{value: 'shiny', label: 'Shiny', href: shinyHref},
|
|
791
|
-
{value: 'minimal', label: 'Minimal', href: minimalHref},
|
|
792
|
-
]
|
|
793
|
-
const colors = [
|
|
794
|
-
{value: 'iceblue', label: 'Ice blue', href: iceblueHref},
|
|
795
|
-
{value: 'purple', label: 'Purple', href: purpleHref},
|
|
796
|
-
]
|
|
797
|
-
|
|
798
|
-
class AppearanceControls extends Component {
|
|
799
|
-
render() {
|
|
800
|
-
return <aside aria-label="Appearance">
|
|
801
|
-
<ThemePicker label="Theme" options={themes} defaultValue="shiny" />
|
|
802
|
-
<ColorPicker label="Colors" options={colors} defaultValue="iceblue" />
|
|
803
|
-
</aside>
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
static dependencies = [ColorPicker, ThemePicker]
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
The `?url` syntax above is supported by Vite; use the equivalent emitted-asset
|
|
811
|
-
mechanism for another bundler. The structural stylesheet is not replaced.
|
|
812
|
-
|
|
813
|
-
See [`themes/README.md`](themes/README.md) for the complete architecture and
|
|
814
|
-
variable families, and [`colors/README.md`](colors/README.md) for the palette
|
|
815
|
-
contract.
|
|
1036
|
+
corresponding root data attribute. `ThemePicker` and `ColorPicker` expose the
|
|
1037
|
+
same operation through the normal value-control contract. The base file and
|
|
1038
|
+
runtime-injected structural stylesheet stay loaded while those two links are
|
|
1039
|
+
replaced.
|
|
816
1040
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
operation, labelled roles, reduced motion, 200% configured text sizing, and
|
|
820
|
-
forced-colors focus visibility. A formal manual screen-reader pass is still a
|
|
821
|
-
release-candidate requirement; automated checks are not a substitute for it.
|
|
1041
|
+
See [the theme contract](themes/README.md) and
|
|
1042
|
+
[the palette contract](colors/README.md) for authoring details.
|
|
822
1043
|
|
|
823
1044
|
## Data workflows
|
|
824
1045
|
|
|
@@ -882,8 +1103,8 @@ For a tree derived from domain state, a callback updates that real source; the
|
|
|
882
1103
|
tree derivation then rebuilds and `deriveTreeNode` resolves the fresh node.
|
|
883
1104
|
`updateTreeNode` is the equivalent pure path-copy operation.
|
|
884
1105
|
|
|
885
|
-
Generic `FilterState` keeps `neutral`, `prefer`, `require`, and `deny`
|
|
886
|
-
values separate from glyphs and transport. `filterByState`,
|
|
1106
|
+
Generic `FilterState` keeps `neutral`, `prefer`, `require`, and `deny`
|
|
1107
|
+
semantic values separate from glyphs and transport. `filterByState`,
|
|
887
1108
|
`deriveFilterPredicate`, and `deriveFilteredItems` consume caller-supplied
|
|
888
1109
|
dimension matchers. `serializeFilterState`/`parseFilterState` round-trip
|
|
889
1110
|
validated version-1 plain data without owning URL or storage access. Unknown
|
|
@@ -902,5 +1123,5 @@ React/Vue adapters, a broad design system, a virtualized production data grid,
|
|
|
902
1123
|
legacy-browser support, or a stable `1.0` API. See the [workspace
|
|
903
1124
|
overview](../../README.md), [API surface](../../docs/API_SURFACE.md),
|
|
904
1125
|
[architecture overview](../../docs/architecture.md),
|
|
905
|
-
[changelog](
|
|
1126
|
+
[changelog](CHANGELOG.md), [contribution guide](../../CONTRIBUTING.md),
|
|
906
1127
|
and [security policy](../../SECURITY.md).
|