declarative-forms 2.0.0-alpha.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/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/index.cjs +2508 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1235 -0
- package/dist/index.d.ts +1235 -0
- package/dist/index.js +2473 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -0
- package/styles/declarative-forms.css +740 -0
- package/styles/theme.css +1293 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- A second stylesheet, `declarative-forms/theme.css`: a modern redesign with
|
|
13
|
+
automatic light and dark mode (`prefers-color-scheme`, overridable with
|
|
14
|
+
`data-dl-theme`), focus rings, fluid widths and a fuller token set. It is a
|
|
15
|
+
standalone alternative to `styles.css`, renders the identical DOM, and mirrors
|
|
16
|
+
the default stylesheet's selectors and specificity so existing overrides keep
|
|
17
|
+
winning. The default stylesheet is unchanged.
|
|
18
|
+
- Under the modern theme, checkboxes are **keyboard-reachable**: the input is
|
|
19
|
+
styled directly with `appearance: none` rather than hidden behind a
|
|
20
|
+
pseudo-element. The default stylesheet keeps its existing behaviour.
|
|
21
|
+
- Two wrapper classes for stacked dialogs: `dl-modal-stacked` on a dialog opened
|
|
22
|
+
on top of another, and `dl-modal-restored` on one revealed again after being
|
|
23
|
+
covered. Both mark a backdrop that is already on screen, so a theme can skip
|
|
24
|
+
its entrance animation. A stacked wrapper also carries `--dl-stack-depth`, the
|
|
25
|
+
number of dialogs underneath it.
|
|
26
|
+
- Under the modern theme, a stacked dialog now **shows the stack**: the dialogs
|
|
27
|
+
below it, which are hidden, are drawn as card edges peeking out above the
|
|
28
|
+
dialog on top — one edge for a single dialog below, two for more. Sized with
|
|
29
|
+
`--dl-stack-peek` and `--dl-stack-inset`. The default stylesheet is unchanged.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Opening or closing a stacked dialog no longer flashes the page under the
|
|
34
|
+
backdrop. The covered dialog was hidden in the same frame the new one started
|
|
35
|
+
fading in, leaving nothing over the page for the length of the animation; the
|
|
36
|
+
modern theme now skips that animation for stacked and restored dialogs. The
|
|
37
|
+
default stylesheet was never affected — it has no animations.
|
|
38
|
+
|
|
39
|
+
## [2.0.0-alpha.0]
|
|
40
|
+
|
|
41
|
+
Complete rewrite in TypeScript with an object-oriented design.
|
|
42
|
+
|
|
43
|
+
**The JavaScript API is a clean break; the rendered DOM is deliberately
|
|
44
|
+
unchanged.** Stylesheets and DOM-based end-to-end selectors written against v1
|
|
45
|
+
keep working. See the [migration guide](https://wolfoo2931.github.io/declarative-forms/migration-v1).
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Discriminated `kind` on field descriptors, making them fully typable.
|
|
50
|
+
- `form.field(name)` — a typed handle with `getValue`, `setValue`, `focus`,
|
|
51
|
+
`setLoading`, `element` and `wrapper`.
|
|
52
|
+
- `FieldRegistry`, so applications can register their own field kinds.
|
|
53
|
+
- Pluggable `TooltipProvider`, with a native zero-dependency default.
|
|
54
|
+
- `html()` / `escapeHtml()` for explicit, opt-in markup.
|
|
55
|
+
- `whenReady()`, `destroy()`, and an unsubscribe return from
|
|
56
|
+
`subscribeOnInput()`.
|
|
57
|
+
- `dl-error` event on file-upload failure.
|
|
58
|
+
- A `--dl-*` design-token layer and a dark theme.
|
|
59
|
+
- Documentation site, and a live example playground under `examples/`.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Package renamed to `declarative-forms`; the export is now named
|
|
64
|
+
(`import { DeclarativeForm }`).
|
|
65
|
+
- Constructor takes a single options object instead of five positional
|
|
66
|
+
arguments.
|
|
67
|
+
- Descriptor callbacks receive one context object instead of per-callback
|
|
68
|
+
positional argument lists.
|
|
69
|
+
- Renamed: `mapFiledsOnEdit` → `mapFieldsOnEdit`,
|
|
70
|
+
`onValuesCalculationFailedMessage` → `onOptionsError`,
|
|
71
|
+
`closeModalIfOpen` → `close`, `updateCalculatedFields` →
|
|
72
|
+
`updateComputedFields`, `mulitple-allowed` → `multiple`.
|
|
73
|
+
- `descriptor.domElement` replaced by `form.field(name)`; the library no longer
|
|
74
|
+
mutates caller-supplied descriptors.
|
|
75
|
+
- Dialog buttons are `<button type="button">` rather than `<div>`, keeping the
|
|
76
|
+
`.btn` class.
|
|
77
|
+
- Content is rendered as text by default; markup requires `html()`.
|
|
78
|
+
- `message` fields no longer appear in `getValues()`.
|
|
79
|
+
- `buttons` and `onConfirm` are independent — a single-button map no longer
|
|
80
|
+
silently replaces `onConfirm`.
|
|
81
|
+
- Test tooling replaced with Vitest, Playwright and tsup.
|
|
82
|
+
|
|
83
|
+
### Removed
|
|
84
|
+
|
|
85
|
+
- All runtime dependencies: `tippy.js`, `@popperjs/core`,
|
|
86
|
+
`@react-hookz/deep-equal`, and the unused `uuid`.
|
|
87
|
+
- The v1 Karma/Jasmine/wdio/Browserify setup, which could not run — its
|
|
88
|
+
packages were referenced by scripts but absent from `devDependencies`.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- `<label for>` pointed at a field name that was never an element id, so no
|
|
93
|
+
field was ever labelled for assistive technology.
|
|
94
|
+
- Checkbox ids collided between a parent form and its array sub-form.
|
|
95
|
+
- `getValues()` could not represent `''`, `0` or `false`.
|
|
96
|
+
- The internal promise list grew without bound and was never cleared.
|
|
97
|
+
- Tabs and tooltips were resolved with `document.querySelector`, so two open
|
|
98
|
+
forms fought over each other's state.
|
|
99
|
+
- A slow options load could overwrite a newer one; loads are now
|
|
100
|
+
generation-guarded.
|
|
101
|
+
- Caller data was interpolated as HTML in 21 places.
|
|
102
|
+
- Option values containing a quote threw a `DOMException`.
|
|
103
|
+
- A keydown listener and a stylesheet were installed as import side effects,
|
|
104
|
+
breaking server-side rendering.
|
|
105
|
+
- `class DlSelect extends HTMLElement` was evaluated at module load, so simply
|
|
106
|
+
importing the package threw `HTMLElement is not defined` in Node and broke
|
|
107
|
+
any server-rendered or prerendered app.
|
|
108
|
+
- `classList.remove` was written as `Element.remove()`, deleting the confirm
|
|
109
|
+
button whenever it showed its loading state.
|
|
110
|
+
- `<dl-option>` was never registered as a custom element.
|
|
111
|
+
- Buttons flashed disabled on every keystroke, and a well-timed click could be
|
|
112
|
+
dropped.
|
|
113
|
+
|
|
114
|
+
[Unreleased]: https://github.com/wolfoo2931/declarative-forms/compare/v2.0.0-alpha.0...HEAD
|
|
115
|
+
[2.0.0-alpha.0]: https://github.com/wolfoo2931/declarative-forms/releases/tag/v2.0.0-alpha.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Wolf Oliver
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# declarative-forms
|
|
2
|
+
|
|
3
|
+
Declarative forms for the web that make front-end development less front-end development.
|
|
4
|
+
|
|
5
|
+
Whenever you need input from a user, you describe the data you need and the rules around it. `declarative-forms` takes care of the front-end work to ask for that information: rendering the fields, wiring updates, loading async option sets, managing nested structures, handling modal stacks, tabs, and keeping the UI in sync as values change.
|
|
6
|
+
|
|
7
|
+
📖 **[Read the documentation](https://wolfoo2931.github.io/declarative-forms/)**
|
|
8
|
+
|
|
9
|
+
You do not have to build the front-end plumbing by hand just to collect structured input. Instead, you provide a schema-like description of the form and the library handles the rest.
|
|
10
|
+
|
|
11
|
+
This is especially useful for complex forms, complex data structures, and workflows where the interface is mostly there to collect and validate user input rather than to render bespoke product UI.
|
|
12
|
+
|
|
13
|
+
## What it is
|
|
14
|
+
|
|
15
|
+
At its core, `declarative-forms` is a framework-agnostic form engine.
|
|
16
|
+
|
|
17
|
+
You define a form like this:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import 'declarative-forms/styles.css';
|
|
21
|
+
import { DeclarativeForm } from 'declarative-forms';
|
|
22
|
+
|
|
23
|
+
const form = new DeclarativeForm({
|
|
24
|
+
fields: [
|
|
25
|
+
{ name: 'title', displayName: 'Title' },
|
|
26
|
+
{
|
|
27
|
+
name: 'language',
|
|
28
|
+
kind: 'select',
|
|
29
|
+
displayName: 'Language',
|
|
30
|
+
options: [
|
|
31
|
+
{ value: 'en', label: 'English' },
|
|
32
|
+
{ value: 'de', label: 'German' },
|
|
33
|
+
],
|
|
34
|
+
defaultValue: 'en',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'repo',
|
|
38
|
+
kind: 'select',
|
|
39
|
+
displayName: 'Repository',
|
|
40
|
+
reloadOnChangeOf: ['language'],
|
|
41
|
+
options: async ({ data }) => {
|
|
42
|
+
const lang = String(data['language'] ?? 'en');
|
|
43
|
+
return [{ value: `repo-${lang}`, label: `Repo (${lang})` }];
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
onConfirm: (values) => console.log(values),
|
|
48
|
+
onCancel: () => {},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
form.openInModal();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is not a generated UI package or a full application framework. It is a runtime for declarative form behavior: you describe the data model and form rules, and it turns that into a working user flow with the UI concerns handled for you.
|
|
55
|
+
|
|
56
|
+
## What makes it different
|
|
57
|
+
|
|
58
|
+
The library is built around a few real strengths that show up in the codebase:
|
|
59
|
+
|
|
60
|
+
- Data-first form definitions instead of manual DOM creation
|
|
61
|
+
- Cross-field reactivity via `reloadOnChangeOf`, `isActive`, `defaultValue`, and `onFormChange`
|
|
62
|
+
- Async option loading and async button predicates
|
|
63
|
+
- Built-in modal stack support for nested dialogs and wizards
|
|
64
|
+
- Tabbed forms and array-of-record editing
|
|
65
|
+
- Automatic handling of many front-end concerns: layout, state sync, stacking, dialog behavior, and reusable field logic
|
|
66
|
+
- Zero runtime dependencies
|
|
67
|
+
- Works in plain JavaScript or TypeScript, not just React/Vue/Svelte
|
|
68
|
+
|
|
69
|
+
The library also includes a few opinionated features that are especially useful for interactive tools:
|
|
70
|
+
|
|
71
|
+
- computed fields that recalculate before confirmation
|
|
72
|
+
- file fields that require a `persistFile` callback to convert a local file into a stored URL
|
|
73
|
+
- custom render fields for embedding arbitrary DOM or widget logic inside a form
|
|
74
|
+
- message, cards, and custom field types for dynamic UI without a separate component library
|
|
75
|
+
|
|
76
|
+
## When to use this library
|
|
77
|
+
|
|
78
|
+
Use `declarative-forms` when the form is more like an application workflow than a static input form.
|
|
79
|
+
|
|
80
|
+
It fits especially well for:
|
|
81
|
+
|
|
82
|
+
- admin panels and settings dialogs
|
|
83
|
+
- configuration flows with conditions and dependencies
|
|
84
|
+
- modal-based wizards with step logic
|
|
85
|
+
- forms whose options depend on other fields
|
|
86
|
+
- array/list editing with nested record forms
|
|
87
|
+
- internal tools that need compact, data-driven UI without a UI framework dependency
|
|
88
|
+
|
|
89
|
+
## Core ideas in the codebase
|
|
90
|
+
|
|
91
|
+
The library is centered on a few runtime concepts:
|
|
92
|
+
|
|
93
|
+
- `DeclarativeForm`: the main runtime that owns rendering, values, tabs, and modal behavior
|
|
94
|
+
- `FieldDescriptor`: a plain object describing each field (`name`, `kind`, `options`, `tab`, `isActive`, etc.)
|
|
95
|
+
- `FormValues`: the current data dictionary keyed by field name
|
|
96
|
+
- `subscribeOnInput`: a way to observe form updates without wiring lots of DOM listeners
|
|
97
|
+
- `ButtonDescriptor`: supports async enable/disable logic and “do not close” actions for wizards
|
|
98
|
+
|
|
99
|
+
This is why the project feels more like a small “form engine” than a typical form component library.
|
|
100
|
+
|
|
101
|
+
## Install
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install declarative-forms
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
And include the default CSS:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import 'declarative-forms/styles.css';
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Example: modal form
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { DeclarativeForm } from 'declarative-forms';
|
|
117
|
+
|
|
118
|
+
const form = new DeclarativeForm({
|
|
119
|
+
fields: [
|
|
120
|
+
{ name: 'name', displayName: 'Name' },
|
|
121
|
+
{
|
|
122
|
+
name: 'role',
|
|
123
|
+
kind: 'select',
|
|
124
|
+
displayName: 'Role',
|
|
125
|
+
options: ['Admin', 'Editor', 'Viewer'],
|
|
126
|
+
defaultValue: 'Editor',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
onConfirm: (values) => {
|
|
130
|
+
console.log('submitted', values);
|
|
131
|
+
},
|
|
132
|
+
onCancel: () => {
|
|
133
|
+
console.log('cancelled');
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
form.openInModal();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Example: reactive field logic
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const form = new DeclarativeForm({
|
|
144
|
+
fields: [
|
|
145
|
+
{
|
|
146
|
+
name: 'source',
|
|
147
|
+
kind: 'select',
|
|
148
|
+
displayName: 'Source',
|
|
149
|
+
options: ['GitHub', 'GitLab'],
|
|
150
|
+
defaultValue: 'GitHub',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'token',
|
|
154
|
+
displayName: 'Token',
|
|
155
|
+
type: 'password',
|
|
156
|
+
isActive: ({ data }) => data['source'] === 'GitLab',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'repo',
|
|
160
|
+
kind: 'select',
|
|
161
|
+
displayName: 'Repository',
|
|
162
|
+
reloadOnChangeOf: ['source'],
|
|
163
|
+
options: async ({ data }) => {
|
|
164
|
+
const source = String(data['source'] ?? 'GitHub');
|
|
165
|
+
return [{ value: `${source}-repo`, label: `${source} repo` }];
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
onConfirm: (values) => console.log(values),
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This is the sort of behavior the library is designed to make practical without building a lot of custom event wiring.
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
Full documentation is at **[wolfoo2931.github.io/declarative-forms](https://wolfoo2931.github.io/declarative-forms/)**:
|
|
178
|
+
|
|
179
|
+
- [Getting started](https://wolfoo2931.github.io/declarative-forms/guide/getting-started)
|
|
180
|
+
- [Field kinds](https://wolfoo2931.github.io/declarative-forms/guide/fields/) — the ten built-in kinds
|
|
181
|
+
- [Reactivity](https://wolfoo2931.github.io/declarative-forms/guide/reactivity) — `isActive`, `reloadOnChangeOf`, computed fields
|
|
182
|
+
- [API reference](https://wolfoo2931.github.io/declarative-forms/reference/api)
|
|
183
|
+
- [Migrating from v1](https://wolfoo2931.github.io/declarative-forms/migration-v1)
|
|
184
|
+
|
|
185
|
+
## Status and limitations
|
|
186
|
+
|
|
187
|
+
Worth knowing before you adopt it:
|
|
188
|
+
|
|
189
|
+
- **Accessibility is a work in progress.** Labels, ids and focusable buttons are correct; dialog semantics, combobox ARIA and keyboard-reachable checkboxes are not there yet. The [accessibility page](https://wolfoo2931.github.io/declarative-forms/accessibility) lists every known gap.
|
|
190
|
+
- **There is no validation framework.** `isActive` handles conditional fields and button predicates gate submission, but there is no rule engine or error-message system.
|
|
191
|
+
- **The rendered DOM is a frozen contract.** Class names, ids and structure are stable across the v1 → v2 rewrite, so existing stylesheets keep working. See the [DOM contract](https://wolfoo2931.github.io/declarative-forms/dom-contract).
|
|
192
|
+
|
|
193
|
+
## Summary
|
|
194
|
+
|
|
195
|
+
`declarative-forms` is best thought of as a declarative form runtime for web dialogs and tool-like workflows.
|
|
196
|
+
|
|
197
|
+
It is for the cases where the main job is not “build another UI component,” but “ask the user for structured data.” You define what needs to be collected, and the library handles the front-end work to gather it: field rendering, behavior, defaults, dynamic changes, nested records, modal stacking, and workflow handling.
|
|
198
|
+
|
|
199
|
+
That is the core difference: instead of writing front-end code for every form interaction, you describe the data and rules, and the runtime takes care of the responsibilities that usually consume most of the work in form-heavy interfaces.
|