@xeplr/ui-factory 1.0.14 → 1.1.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/AUTHORING.md +29 -5
- package/README.md +140 -18
- package/package.json +1 -1
- package/src/controls.js +19 -13
- package/src/designs/BuilderSample.jsx +77 -19
- package/src/designs/ControlView.jsx +10 -1
- package/src/designs/FlowRunnerSample.jsx +6 -4
- package/src/designs/ListView.jsx +40 -21
- package/src/designs/Palette.jsx +2 -1
- package/src/designs/PropertyPanel.jsx +57 -1
- package/src/designs/ScreenEditorSample.jsx +97 -0
- package/src/designs/ScreenModal.jsx +2 -2
- package/src/designs/ScreenSample.jsx +92 -41
- package/src/designs/factory.css +170 -76
- package/src/designs/index.js +4 -0
- package/src/designs/styles.js +11 -1
- package/src/generate.js +35 -10
- package/src/hooks.js +1 -1
- package/src/index.js +6 -2
- package/src/model.js +2 -1
- package/src/pages.jsx +15 -3
- package/src/presets.js +324 -0
- package/src/remote.js +7 -1
- package/src/scaffold.js +2 -2
- package/src/screenSource.js +91 -0
- package/src/tableSchema.js +109 -6
- package/src/useFactoryBuilder.js +65 -16
- package/src/useFactoryScreen.js +74 -39
- package/src/useScreenSource.js +81 -0
- package/src/validateDocument.js +5 -1
- package/src/values.js +18 -5
package/src/designs/factory.css
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/* @xeplr/ui-factory — builder and screen.
|
|
2
|
-
Colours come from the
|
|
3
|
-
a host that defines none still
|
|
2
|
+
Colours come from the app's theme — @xeplr/ui-account's --xeplr-* variables
|
|
3
|
+
(theme.css) — each with a light fallback, so a host that defines none still
|
|
4
|
+
gets a finished look.
|
|
5
|
+
|
|
6
|
+
THE REAL NAMES, NOT LOOK-ALIKES. This file used to read --xeplr-text,
|
|
7
|
+
--xeplr-surface, --xeplr-border and --xeplr-muted, which no theme defines —
|
|
8
|
+
so every fallback won, and in a dark app a screen came out as a white sheet
|
|
9
|
+
while the @xeplr/ui-table inside it (which reads the real names) came out
|
|
10
|
+
dark. Everything below resolves through the --xf-* tokens, and those read
|
|
11
|
+
the variables theme.css actually sets. "Elevated" is --xeplr-bg-tertiary in
|
|
12
|
+
every built-in theme (white in light, the lighter grey in dark), which is
|
|
13
|
+
what a sheet, a panel and a popup are.
|
|
4
14
|
|
|
5
15
|
ONE SIZE UNIT, --xf-u: one pixel at the screen's design width (--xf-width,
|
|
6
16
|
set from the document). At that width or wider it IS a pixel — text is
|
|
@@ -12,11 +22,24 @@
|
|
|
12
22
|
.xeplr-factory-flow-builder {
|
|
13
23
|
container-type: inline-size;
|
|
14
24
|
--xf-u: min(1px, calc(100cqw / var(--xf-width, 800)));
|
|
25
|
+
}
|
|
26
|
+
.xeplr-factory-screen,
|
|
27
|
+
.xeplr-factory-canvas,
|
|
28
|
+
.xeplr-factory-flow-builder,
|
|
29
|
+
.xeplr-factory-builder,
|
|
30
|
+
.xeplr-factory-modal-backdrop {
|
|
15
31
|
--xf-accent: var(--xeplr-accent, #2563eb);
|
|
16
|
-
--xf-ink: var(--xeplr-text, #
|
|
17
|
-
--xf-
|
|
18
|
-
--xf-
|
|
19
|
-
--xf-
|
|
32
|
+
--xf-accent-ink: var(--xeplr-accent-text, #ffffff);
|
|
33
|
+
--xf-ink: var(--xeplr-text-primary, #1a1a19);
|
|
34
|
+
--xf-muted: var(--xeplr-text-secondary, #6b6a63);
|
|
35
|
+
--xf-border: var(--xeplr-border-primary, #d9d8d0);
|
|
36
|
+
--xf-surface: var(--xeplr-bg-tertiary, #ffffff);
|
|
37
|
+
--xf-plane: var(--xeplr-bg-primary, #f7f7f5);
|
|
38
|
+
--xf-hover: var(--xeplr-bg-hover, #f0f0ee);
|
|
39
|
+
--xf-field-bg: var(--xeplr-input-bg, var(--xf-surface));
|
|
40
|
+
--xf-field-border: var(--xeplr-input-border, var(--xf-border));
|
|
41
|
+
--xf-good: var(--xeplr-success, #1d7a4c);
|
|
42
|
+
--xf-warn: var(--xeplr-warning, #a86a00);
|
|
20
43
|
--xf-danger: var(--xeplr-danger, #c2372e);
|
|
21
44
|
}
|
|
22
45
|
|
|
@@ -28,21 +51,19 @@
|
|
|
28
51
|
host with --xeplr-factory-paper. */
|
|
29
52
|
.xeplr-factory-screen {
|
|
30
53
|
position: relative; width: 100%;
|
|
54
|
+
/* Centred in whatever it is given: shown at its design width, a screen that
|
|
55
|
+
hugs the left edge of a wide page looks abandoned there. */
|
|
56
|
+
margin-inline: auto;
|
|
57
|
+
box-sizing: border-box;
|
|
31
58
|
color: var(--xf-ink);
|
|
32
59
|
background: var(--xeplr-factory-paper, var(--xf-surface));
|
|
33
|
-
border
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.xeplr-factory-screen
|
|
40
|
-
.xeplr-factory-status {
|
|
41
|
-
display: flex; justify-content: space-between; gap: 12px; min-height: 18px;
|
|
42
|
-
padding: 4px 2px 8px; font-size: 12px; color: var(--xf-muted);
|
|
43
|
-
}
|
|
44
|
-
.xeplr-factory-status--saved { color: var(--xeplr-status-good, #1d7a4c); }
|
|
45
|
-
.xeplr-factory-status--invalid, .xeplr-factory-status--error { color: var(--xf-danger); }
|
|
60
|
+
border: 1px solid var(--xf-border);
|
|
61
|
+
border-radius: 10px;
|
|
62
|
+
}
|
|
63
|
+
/* A screen of lists only (nothing to fill in) paints no sheet: the list is
|
|
64
|
+
its own card, and a sheet around it is a second frame with a block of empty
|
|
65
|
+
paper under the rows. */
|
|
66
|
+
.xeplr-factory-screen--bare { background: none; border: none; }
|
|
46
67
|
.xeplr-factory-page { position: relative; width: 100%; margin: 0; }
|
|
47
68
|
.xeplr-factory-node { position: absolute; box-sizing: border-box; }
|
|
48
69
|
|
|
@@ -66,15 +87,17 @@
|
|
|
66
87
|
.xeplr-factory-required { color: var(--xf-danger); }
|
|
67
88
|
.xeplr-factory-input {
|
|
68
89
|
flex: 1; min-height: 0; width: 100%; box-sizing: border-box;
|
|
69
|
-
/* The
|
|
70
|
-
|
|
71
|
-
dark ink
|
|
72
|
-
font: inherit; color: var(--xf-ink); background: var(--xf-
|
|
73
|
-
border: 1px solid var(--xf-border); border-radius: calc(6 * var(--xf-u));
|
|
90
|
+
/* The app's input colours — unless the SCREEN sets its own colours, when
|
|
91
|
+
screenStyle points --xf-ink / --xf-field-bg at those, so a screen designed
|
|
92
|
+
in dark ink on paper never gets a dark theme's box under that ink. */
|
|
93
|
+
font: inherit; color: var(--xf-ink); background: var(--xf-field-bg);
|
|
94
|
+
border: 1px solid var(--xf-field-border); border-radius: calc(6 * var(--xf-u));
|
|
74
95
|
padding: 0 calc(10 * var(--xf-u));
|
|
96
|
+
transition: border-color .12s, box-shadow .12s;
|
|
75
97
|
}
|
|
98
|
+
.xeplr-factory-input:hover { border-color: color-mix(in srgb, var(--xf-accent) 45%, var(--xf-field-border)); }
|
|
76
99
|
.xeplr-factory-textarea { padding-block: calc(8 * var(--xf-u)); resize: none; }
|
|
77
|
-
.xeplr-factory-input:focus { outline:
|
|
100
|
+
.xeplr-factory-input:focus { outline: none; border-color: var(--xf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--xf-accent) 25%, transparent); }
|
|
78
101
|
.xeplr-factory-field.has-error .xeplr-factory-input { border-color: var(--xf-danger); }
|
|
79
102
|
.xeplr-factory-error {
|
|
80
103
|
/* In the gap BELOW the field, out of flow: a message appearing must not
|
|
@@ -92,7 +115,10 @@
|
|
|
92
115
|
list of options cannot cover the next field. */
|
|
93
116
|
.xeplr-factory-choices { display: flex; gap: calc(6 * var(--xf-u)) calc(16 * var(--xf-u)); overflow: auto; min-height: 0; }
|
|
94
117
|
.xeplr-factory-choices--vertical { flex-direction: column; }
|
|
95
|
-
|
|
118
|
+
/* Side by side, the options sit in the band an input box would fill — so a
|
|
119
|
+
row of radios beside a dropdown lines up with the dropdown's box, not with
|
|
120
|
+
its label. */
|
|
121
|
+
.xeplr-factory-choices--horizontal { flex: 1; flex-direction: row; flex-wrap: wrap; align-items: center; align-content: center; }
|
|
96
122
|
.xeplr-factory-choice { display: flex; align-items: center; gap: calc(8 * var(--xf-u)); cursor: pointer; color: var(--xf-ink); }
|
|
97
123
|
.xeplr-factory-choice input { width: calc(16 * var(--xf-u)); height: calc(16 * var(--xf-u)); min-width: 11px; min-height: 11px; margin: 0; accent-color: var(--xf-accent); }
|
|
98
124
|
|
|
@@ -100,48 +126,81 @@
|
|
|
100
126
|
.xeplr-factory-file { display: flex; align-items: center; gap: calc(8 * var(--xf-u)); min-width: 0; }
|
|
101
127
|
.xeplr-factory-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--xf-ink); }
|
|
102
128
|
.xeplr-factory-file-clear {
|
|
103
|
-
flex: none; border: 1px solid var(--xf-border); background: var(--xf-
|
|
129
|
+
flex: none; border: 1px solid var(--xf-field-border); background: var(--xf-field-bg); color: var(--xf-muted);
|
|
104
130
|
border-radius: calc(6 * var(--xf-u)); padding: calc(3 * var(--xf-u)) calc(8 * var(--xf-u)); cursor: pointer; font: inherit;
|
|
105
131
|
}
|
|
106
132
|
.xeplr-factory-file-clear:hover { color: var(--xf-ink); border-color: var(--xf-accent); }
|
|
107
133
|
.xeplr-factory-file-input { padding: calc(5 * var(--xf-u)); }
|
|
108
134
|
|
|
109
135
|
/* ── stepper ──────────────────────────────────────────────────────────────
|
|
110
|
-
The journey across the top: a circle per step, joined by a line. The line
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
The journey across the top: a circle per step, joined by a line. The line
|
|
137
|
+
runs from the EDGE of one circle to the edge of the next, with a small gap —
|
|
138
|
+
never through them. (It used to run centre to centre behind the circles,
|
|
139
|
+
and each step's line was painted after the previous step's circle, so it
|
|
140
|
+
struck straight through the numbers.) --xf-mark is the circle's size. */
|
|
141
|
+
.xeplr-factory-stepper {
|
|
142
|
+
--xf-mark: max(22px, calc(28 * var(--xf-u)));
|
|
143
|
+
display: flex; align-items: center; width: 100%; height: 100%; overflow: hidden;
|
|
144
|
+
}
|
|
113
145
|
.xeplr-factory-steps { display: flex; align-items: flex-start; width: 100%; margin: 0; padding: 0; list-style: none; }
|
|
114
146
|
.xeplr-factory-step { flex: 1; position: relative; min-width: 0; text-align: center; }
|
|
115
147
|
.xeplr-factory-step + .xeplr-factory-step::before {
|
|
116
|
-
content: ''; position: absolute;
|
|
148
|
+
content: ''; position: absolute;
|
|
149
|
+
top: calc(var(--xf-mark) / 2 - 1px); height: 2px; border-radius: 1px;
|
|
150
|
+
left: calc(-50% + var(--xf-mark) / 2 + 8px);
|
|
151
|
+
right: calc(50% + var(--xf-mark) / 2 + 8px);
|
|
117
152
|
background: var(--xf-border);
|
|
153
|
+
transition: background .2s;
|
|
118
154
|
}
|
|
119
155
|
.xeplr-factory-step.is-active::before, .xeplr-factory-step.is-done::before { background: var(--xf-accent); }
|
|
120
156
|
.xeplr-factory-step-button {
|
|
121
|
-
position: relative; display: flex; flex-direction: column; align-items: center; gap: calc(
|
|
157
|
+
position: relative; display: flex; flex-direction: column; align-items: center; gap: calc(6 * var(--xf-u));
|
|
122
158
|
width: 100%; padding: 0; border: none; background: none; color: var(--xf-muted); font: inherit; cursor: pointer;
|
|
123
159
|
}
|
|
124
160
|
.xeplr-factory-step-mark {
|
|
125
|
-
display: flex; align-items: center; justify-content: center;
|
|
126
|
-
width:
|
|
127
|
-
border-radius: 50%;
|
|
161
|
+
display: flex; align-items: center; justify-content: center; box-sizing: border-box;
|
|
162
|
+
width: var(--xf-mark); height: var(--xf-mark);
|
|
163
|
+
border-radius: 50%; border: 2px solid var(--xf-border); background: var(--xf-surface);
|
|
164
|
+
color: var(--xf-muted); font-weight: 600; font-size: .9em; line-height: 1;
|
|
165
|
+
transition: background .2s, border-color .2s, box-shadow .2s, transform .08s;
|
|
128
166
|
}
|
|
167
|
+
.xeplr-factory-step-button:hover .xeplr-factory-step-mark { border-color: var(--xf-accent); }
|
|
168
|
+
.xeplr-factory-step-button:active .xeplr-factory-step-mark { transform: scale(.92); }
|
|
129
169
|
.xeplr-factory-step-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
130
|
-
.xeplr-factory-step.is-
|
|
131
|
-
|
|
170
|
+
.xeplr-factory-step.is-done .xeplr-factory-step-mark { background: var(--xf-accent); border-color: var(--xf-accent); color: var(--xf-accent-ink); }
|
|
171
|
+
/* Where you are: filled, with a soft halo. */
|
|
172
|
+
.xeplr-factory-step.is-active .xeplr-factory-step-mark {
|
|
173
|
+
background: var(--xf-accent); border-color: var(--xf-accent); color: var(--xf-accent-ink);
|
|
174
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--xf-accent) 22%, transparent);
|
|
175
|
+
}
|
|
176
|
+
.xeplr-factory-step.is-done .xeplr-factory-step-button { color: var(--xf-ink); }
|
|
132
177
|
.xeplr-factory-step.is-active .xeplr-factory-step-button { color: var(--xf-accent); font-weight: 600; }
|
|
178
|
+
.xeplr-factory-step-button:focus { outline: none; }
|
|
179
|
+
.xeplr-factory-step-button:focus-visible .xeplr-factory-step-mark { outline: 2px solid var(--xf-accent); outline-offset: 3px; }
|
|
133
180
|
/* A step the app has ruled out: still shown, so the journey reads the same,
|
|
134
181
|
but plainly not one of the stops. */
|
|
135
182
|
.xeplr-factory-step.is-off .xeplr-factory-step-button { opacity: 0.4; cursor: default; }
|
|
136
183
|
.xeplr-factory-step.is-off .xeplr-factory-step-label { text-decoration: line-through; }
|
|
137
184
|
|
|
138
|
-
/*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
/* ── the footer ─────────────────────────────────────────────────────────────
|
|
186
|
+
ONE bar under the fields: Back on the left, where you are and what the save
|
|
187
|
+
is doing in the middle, the ways forward on the right. Inset by the screen's
|
|
188
|
+
own margin (--xf-margin, set by ScreenSample) so the buttons line up with
|
|
189
|
+
the fields' edges instead of poking out past them. */
|
|
190
|
+
.xeplr-factory-footer {
|
|
191
|
+
display: flex; align-items: center; gap: 10px;
|
|
192
|
+
margin-top: calc(8 * var(--xf-u));
|
|
193
|
+
padding: 14px var(--xf-margin, 16px);
|
|
194
|
+
border-top: 1px solid var(--xf-border);
|
|
195
|
+
}
|
|
196
|
+
.xeplr-factory-footer-mid {
|
|
197
|
+
flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
198
|
+
color: var(--xf-muted); font-size: 12px; text-align: center;
|
|
199
|
+
}
|
|
200
|
+
.xeplr-factory-footer-mid:first-child { align-items: flex-start; text-align: left; }
|
|
201
|
+
.xeplr-factory-footer-status--saved { color: var(--xf-good); }
|
|
202
|
+
.xeplr-factory-footer-status--invalid, .xeplr-factory-footer-status--error { color: var(--xf-danger); }
|
|
203
|
+
.xeplr-factory-footer button[disabled] { opacity: 0.4; cursor: default; }
|
|
145
204
|
|
|
146
205
|
/* Which step the builder is on. */
|
|
147
206
|
.xeplr-factory-building { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 auto 8px; padding: 6px 0; }
|
|
@@ -159,37 +218,42 @@
|
|
|
159
218
|
|
|
160
219
|
/* ── list ───────────────────────────────────────────────────────────────── */
|
|
161
220
|
.xeplr-factory-list {
|
|
162
|
-
display: flex; flex-direction: column; width: 100%;
|
|
221
|
+
display: flex; flex-direction: column; width: 100%; box-sizing: border-box; overflow: auto;
|
|
222
|
+
/* As tall as its rows, up to the box it was given — never a frame around a
|
|
223
|
+
block of empty space under two records. */
|
|
224
|
+
height: auto; max-height: 100%;
|
|
163
225
|
font-size: max(10px, calc(var(--xf-font, 14) * var(--xf-u)));
|
|
164
|
-
border: 1px solid var(--xf-border); border-radius:
|
|
226
|
+
border: 1px solid var(--xf-border); border-radius: 10px; background: var(--xf-surface);
|
|
165
227
|
}
|
|
166
|
-
.xeplr-factory-list-head { display: flex; align-items: center; gap: 10px; padding:
|
|
167
|
-
.xeplr-factory-list-title { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
228
|
+
.xeplr-factory-list-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--xf-border); }
|
|
229
|
+
.xeplr-factory-list-title { font-weight: 600; font-size: 1.05em; color: var(--xf-ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
168
230
|
.xeplr-factory-list-new {
|
|
169
|
-
font: inherit; font-weight: 600; font-size: .9em; padding:
|
|
170
|
-
color: var(--xf-accent); background:
|
|
231
|
+
font: inherit; font-weight: 600; font-size: .9em; padding: 5px 12px; border-radius: 6px; cursor: pointer;
|
|
232
|
+
color: var(--xf-accent-ink); background: var(--xf-accent); border: 1px solid var(--xf-accent);
|
|
171
233
|
}
|
|
234
|
+
/* The table IS the card here: the list's frame is enough, so the table's own
|
|
235
|
+
border and corners go, and its toolbar becomes the list's header. */
|
|
236
|
+
.xeplr-factory-list--table { overflow: hidden; }
|
|
237
|
+
.xeplr-factory-list--table .xeplr-table-container { border: none; border-radius: 0; background: transparent; }
|
|
238
|
+
.xeplr-factory-list--table .xeplr-table-toolbar { padding: 12px 14px; background: transparent; gap: 10px; }
|
|
239
|
+
.xeplr-factory-list--design { height: 100%; }
|
|
172
240
|
.xeplr-factory-list-empty { padding: 16px; color: var(--xf-muted); text-align: center; }
|
|
173
241
|
.xeplr-factory-error-inline { color: var(--xf-danger); font-size: .9em; }
|
|
174
242
|
.xeplr-factory-list-mock { width: 100%; border-collapse: collapse; }
|
|
175
243
|
.xeplr-factory-list-mock th { text-align: left; font-weight: 600; font-size: .9em; padding: calc(6 * var(--xf-u)) calc(10 * var(--xf-u)); border-bottom: 1px solid var(--xf-border); color: var(--xf-muted); }
|
|
176
244
|
.xeplr-factory-list-mock td { padding: calc(8 * var(--xf-u)) calc(10 * var(--xf-u)); border-bottom: 1px solid var(--xf-border); }
|
|
177
|
-
.xeplr-factory-list-bar { display: block; height: calc(8 * var(--xf-u)); width: 70%; border-radius: 4px; background:
|
|
245
|
+
.xeplr-factory-list-bar { display: block; height: calc(8 * var(--xf-u)); width: 70%; border-radius: 4px; background: var(--xf-border); }
|
|
178
246
|
.xeplr-factory-list .xeplr-table-row.xeplr-factory-row-open td,
|
|
179
247
|
.xeplr-factory-list tr.xeplr-factory-row-open td { background: color-mix(in srgb, var(--xf-accent) 10%, transparent); }
|
|
180
248
|
|
|
181
249
|
/* ── the builder ────────────────────────────────────────────────────────── */
|
|
182
250
|
.xeplr-factory-builder {
|
|
183
251
|
display: flex; flex-direction: column; height: 100%; min-height: 560px;
|
|
184
|
-
color: var(--
|
|
185
|
-
--xf-accent: var(--xeplr-accent, #2563eb);
|
|
186
|
-
--xf-border: var(--xeplr-border, #d9d8d0);
|
|
187
|
-
--xf-muted: var(--xeplr-muted, #6b6a63);
|
|
188
|
-
--xf-danger: var(--xeplr-danger, #c2372e);
|
|
252
|
+
color: var(--xf-ink); font-size: 13px;
|
|
189
253
|
}
|
|
190
254
|
.xeplr-factory-bar {
|
|
191
255
|
display: flex; align-items: center; gap: 10px; padding: 8px 12px;
|
|
192
|
-
border-bottom: 1px solid var(--xf-border); background: var(--
|
|
256
|
+
border-bottom: 1px solid var(--xf-border); background: var(--xf-surface);
|
|
193
257
|
}
|
|
194
258
|
.xeplr-factory-name {
|
|
195
259
|
flex: 1; min-width: 0; font: inherit; font-size: 16px; font-weight: 600;
|
|
@@ -200,27 +264,41 @@
|
|
|
200
264
|
.xeplr-factory-problems { font-size: 12px; color: var(--xf-muted); white-space: nowrap; cursor: help; }
|
|
201
265
|
.xeplr-factory-problems.is-loud { color: var(--xf-danger); font-weight: 600; }
|
|
202
266
|
.xeplr-factory-primary, .xeplr-factory-secondary, .xeplr-factory-danger {
|
|
203
|
-
font: inherit; font-weight: 600; padding:
|
|
267
|
+
font: inherit; font-weight: 600; padding: 7px 16px; border-radius: 6px; cursor: pointer; border: 1px solid var(--xf-accent);
|
|
268
|
+
}
|
|
269
|
+
.xeplr-factory-primary { background: var(--xf-accent); color: var(--xf-accent-ink); }
|
|
270
|
+
.xeplr-factory-primary, .xeplr-factory-secondary, .xeplr-factory-danger, .xeplr-factory-list-new {
|
|
271
|
+
transition: background .12s, filter .12s, transform .08s, box-shadow .12s;
|
|
272
|
+
}
|
|
273
|
+
.xeplr-factory-primary:hover:not(:disabled) { filter: brightness(1.08); }
|
|
274
|
+
.xeplr-factory-secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--xf-accent) 12%, transparent); }
|
|
275
|
+
.xeplr-factory-list-new:hover { filter: brightness(1.08); }
|
|
276
|
+
.xeplr-factory-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--xf-danger) 12%, transparent); }
|
|
277
|
+
/* A press you can see — the button gives a little. */
|
|
278
|
+
.xeplr-factory-primary:active:not(:disabled), .xeplr-factory-secondary:active:not(:disabled),
|
|
279
|
+
.xeplr-factory-danger:active:not(:disabled), .xeplr-factory-list-new:active { transform: scale(.96); }
|
|
280
|
+
.xeplr-factory-primary:focus-visible, .xeplr-factory-secondary:focus-visible,
|
|
281
|
+
.xeplr-factory-danger:focus-visible, .xeplr-factory-list-new:focus-visible {
|
|
282
|
+
outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--xf-accent) 35%, transparent);
|
|
204
283
|
}
|
|
205
|
-
.xeplr-factory-primary { background: var(--xf-accent); color: #fff; }
|
|
206
284
|
.xeplr-factory-primary:disabled { opacity: .6; }
|
|
207
285
|
.xeplr-factory-secondary { background: transparent; color: var(--xf-accent); }
|
|
208
286
|
.xeplr-factory-danger { background: transparent; color: var(--xf-danger); border-color: var(--xf-danger); }
|
|
209
287
|
.xeplr-factory-save-error { padding: 6px 12px; color: var(--xf-danger); background: color-mix(in srgb, var(--xf-danger) 7%, transparent); font-size: 12px; }
|
|
210
288
|
|
|
211
289
|
.xeplr-factory-body { flex: 1; min-height: 0; display: flex; }
|
|
212
|
-
.xeplr-factory-bar-status--saved { color: var(--
|
|
290
|
+
.xeplr-factory-bar-status--saved { color: var(--xf-good); }
|
|
213
291
|
.xeplr-factory-bar-status--invalid, .xeplr-factory-bar-status--error { color: var(--xf-danger); }
|
|
214
|
-
.xeplr-factory-preview { flex: 1; min-height: 0; overflow: auto; padding: 16px; background: var(--
|
|
215
|
-
.xeplr-factory-preview > .xeplr-factory-screen { max-width: 960px; margin: 0 auto; background: var(--
|
|
292
|
+
.xeplr-factory-preview { flex: 1; min-height: 0; overflow: auto; padding: 16px; background: var(--xf-plane); }
|
|
293
|
+
.xeplr-factory-preview > .xeplr-factory-screen { max-width: 960px; margin: 0 auto; background: var(--xf-surface); border: 1px solid var(--xf-border); border-radius: 8px; }
|
|
216
294
|
|
|
217
295
|
/* palette */
|
|
218
|
-
.xeplr-factory-palette { width: 168px; flex: 0 0 auto; overflow-y: auto; padding: 10px; border-right: 1px solid var(--xf-border); background: var(--
|
|
296
|
+
.xeplr-factory-palette { width: 168px; flex: 0 0 auto; overflow-y: auto; padding: 10px; border-right: 1px solid var(--xf-border); background: var(--xf-surface); }
|
|
219
297
|
.xeplr-factory-palette-group + .xeplr-factory-palette-group { margin-top: 12px; }
|
|
220
298
|
.xeplr-factory-palette-title { margin: 0 0 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--xf-muted); }
|
|
221
299
|
.xeplr-factory-palette-item {
|
|
222
300
|
display: flex; align-items: center; gap: 8px; width: 100%; margin-bottom: 4px; padding: 7px 8px;
|
|
223
|
-
font: inherit; text-align: left; color: inherit; background: var(--
|
|
301
|
+
font: inherit; text-align: left; color: inherit; background: var(--xf-surface);
|
|
224
302
|
border: 1px solid var(--xf-border); border-radius: 6px; cursor: grab;
|
|
225
303
|
}
|
|
226
304
|
.xeplr-factory-palette-item:hover { border-color: var(--xf-accent); }
|
|
@@ -236,14 +314,14 @@
|
|
|
236
314
|
.xeplr-factory-palette-icon--textarea { height: 14px; }
|
|
237
315
|
|
|
238
316
|
/* canvas */
|
|
239
|
-
.xeplr-factory-stage { flex: 1; min-width: 0; min-height: 0; display: flex; justify-content: center; padding: 12px; box-sizing: border-box; background: var(--
|
|
317
|
+
.xeplr-factory-stage { flex: 1; min-width: 0; min-height: 0; display: flex; justify-content: center; padding: 12px; box-sizing: border-box; background: var(--xf-plane); }
|
|
240
318
|
/* Exactly the screen's design width, never wider: what you design is the size it is shown at. */
|
|
241
319
|
.xeplr-factory-frame { flex: 1; min-width: 0; height: 100%; display: flex; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
|
|
242
320
|
.xeplr-factory-canvas {
|
|
243
321
|
flex: 1; height: 100%;
|
|
244
322
|
background:
|
|
245
323
|
radial-gradient(circle, var(--xeplr-border-secondary, #e4e3db) 1px, transparent 1px) 0 0 / 20px 20px,
|
|
246
|
-
var(--
|
|
324
|
+
var(--xf-surface);
|
|
247
325
|
}
|
|
248
326
|
.xeplr-factory-design-node { width: 100%; height: 100%; box-sizing: border-box; border-radius: 4px; outline: 1px dashed transparent; cursor: move; }
|
|
249
327
|
.xeplr-factory-design-node:hover { outline-color: color-mix(in srgb, var(--xf-accent) 45%, transparent); }
|
|
@@ -257,7 +335,21 @@
|
|
|
257
335
|
}
|
|
258
336
|
|
|
259
337
|
/* property panel */
|
|
260
|
-
.xeplr-factory-panel { width: 280px; flex: 0 0 auto; overflow-y: auto; padding: 10px 12px; border-left: 1px solid var(--xf-border); background: var(--
|
|
338
|
+
.xeplr-factory-panel { width: 280px; flex: 0 0 auto; overflow-y: auto; padding: 10px 12px; border-left: 1px solid var(--xf-border); background: var(--xf-surface); }
|
|
339
|
+
.xeplr-factory-fieldtype { padding: 2px 0 12px; margin-bottom: 4px; border-bottom: 1px solid var(--xf-border); }
|
|
340
|
+
.xeplr-factory-suggest {
|
|
341
|
+
margin-top: 8px; padding: 8px 10px; border-radius: 6px; font-size: 12px; line-height: 1.45;
|
|
342
|
+
color: var(--xf-ink); background: color-mix(in srgb, var(--xf-accent) 10%, transparent);
|
|
343
|
+
border: 1px solid color-mix(in srgb, var(--xf-accent) 35%, transparent);
|
|
344
|
+
}
|
|
345
|
+
.xeplr-factory-suggest-actions { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
|
|
346
|
+
.xeplr-factory-suggest-actions button { padding: 4px 10px; font-size: 12px; }
|
|
347
|
+
.xeplr-factory-hints {
|
|
348
|
+
font: inherit; font-size: 12px; white-space: nowrap; cursor: pointer; padding: 3px 10px; border-radius: 999px;
|
|
349
|
+
color: var(--xf-accent); background: color-mix(in srgb, var(--xf-accent) 10%, transparent);
|
|
350
|
+
border: 1px solid color-mix(in srgb, var(--xf-accent) 35%, transparent);
|
|
351
|
+
}
|
|
352
|
+
.xeplr-factory-hints:hover { background: color-mix(in srgb, var(--xf-accent) 18%, transparent); }
|
|
261
353
|
.xeplr-factory-panel-empty { color: var(--xf-muted); line-height: 1.5; }
|
|
262
354
|
.xeplr-factory-panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
|
263
355
|
.xeplr-factory-panel-type { font-weight: 700; font-size: 14px; }
|
|
@@ -268,7 +360,7 @@
|
|
|
268
360
|
.xeplr-factory-prop-label { display: block; margin-bottom: 3px; font-weight: 600; font-size: 12px; }
|
|
269
361
|
.xeplr-factory-prop-input {
|
|
270
362
|
width: 100%; box-sizing: border-box; font: inherit; padding: 5px 7px;
|
|
271
|
-
border: 1px solid var(--xf-border); border-radius: 5px; background:
|
|
363
|
+
border: 1px solid var(--xf-field-border); border-radius: 5px; background: var(--xf-field-bg); color: var(--xf-ink);
|
|
272
364
|
}
|
|
273
365
|
.xeplr-factory-prop.has-error .xeplr-factory-prop-input { border-color: var(--xf-danger); }
|
|
274
366
|
/* An editor made of two inputs — a set to pick from, and the list it fills in. */
|
|
@@ -281,7 +373,7 @@
|
|
|
281
373
|
|
|
282
374
|
.xeplr-factory-segment { display: flex; margin-bottom: 8px; border: 1px solid var(--xf-border); border-radius: 6px; overflow: hidden; }
|
|
283
375
|
.xeplr-factory-segment button { flex: 1; font: inherit; font-size: 12px; padding: 5px; border: none; background: transparent; cursor: pointer; color: inherit; }
|
|
284
|
-
.xeplr-factory-segment button.is-on { background: var(--xf-accent); color:
|
|
376
|
+
.xeplr-factory-segment button.is-on { background: var(--xf-accent); color: var(--xf-accent-ink); }
|
|
285
377
|
.xeplr-factory-options-head, .xeplr-factory-option-row { display: grid; grid-template-columns: 1fr 1fr 24px; gap: 4px; align-items: center; }
|
|
286
378
|
.xeplr-factory-options-head { font-size: 11px; color: var(--xf-muted); margin-bottom: 3px; }
|
|
287
379
|
.xeplr-factory-option-row { margin-bottom: 4px; }
|
|
@@ -298,24 +390,26 @@
|
|
|
298
390
|
.xeplr-factory-modal-backdrop {
|
|
299
391
|
position: fixed; inset: 0; z-index: 10000; display: flex; align-items: flex-start; justify-content: center;
|
|
300
392
|
padding: 4vh 2vw; box-sizing: border-box; overflow: auto;
|
|
301
|
-
background: rgba(20, 20, 18, .45);
|
|
393
|
+
background: var(--xeplr-bg-overlay, rgba(20, 20, 18, .45));
|
|
302
394
|
}
|
|
303
395
|
.xeplr-factory-modal {
|
|
304
|
-
max-width: 96vw; background: var(--
|
|
396
|
+
max-width: 96vw; background: var(--xf-surface); color: var(--xf-ink);
|
|
305
397
|
border-radius: 10px; box-shadow: 0 12px 40px rgba(0, 0, 0, .25); outline: none;
|
|
306
398
|
}
|
|
307
|
-
.xeplr-factory-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid var(--
|
|
399
|
+
.xeplr-factory-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid var(--xf-border); }
|
|
308
400
|
.xeplr-factory-modal-title { font-weight: 700; font-size: 15px; }
|
|
309
|
-
.xeplr-factory-modal-close { border: none; background: none; font-size: 22px; line-height: 1; cursor: pointer; color: var(--
|
|
401
|
+
.xeplr-factory-modal-close { border: none; background: none; font-size: 22px; line-height: 1; cursor: pointer; color: var(--xf-muted); padding: 2px 6px; }
|
|
310
402
|
.xeplr-factory-modal-body { padding: 8px 24px 16px; }
|
|
403
|
+
/* The popup is the frame; the screen inside it does not draw a second one. */
|
|
404
|
+
.xeplr-factory-modal-body > .xeplr-factory-screen { border: none; border-radius: 0; }
|
|
311
405
|
|
|
312
406
|
/* ── publish result ─────────────────────────────────────────────────────── */
|
|
313
407
|
.xeplr-factory-publish { display: flex; align-items: flex-start; gap: 10px; padding: 8px 12px; font-size: 12.5px; border-bottom: 1px solid var(--xf-border); }
|
|
314
408
|
.xeplr-factory-publish > span { flex: 1; }
|
|
315
|
-
.xeplr-factory-publish.is-ok { color: var(--
|
|
409
|
+
.xeplr-factory-publish.is-ok { color: var(--xf-good); background: color-mix(in srgb, var(--xf-good) 7%, transparent); }
|
|
316
410
|
.xeplr-factory-publish.is-error { color: var(--xf-danger); background: color-mix(in srgb, var(--xf-danger) 7%, transparent); flex-wrap: wrap; }
|
|
317
|
-
.xeplr-factory-publish-detail { flex-basis: 100%; order: 3; margin: 4px 0 0; padding: 8px; max-height: 220px; overflow: auto; font-size: 11.5px; color: var(--
|
|
318
|
-
.xeplr-factory-publish.is-confirm { align-items: center; color: var(--
|
|
411
|
+
.xeplr-factory-publish-detail { flex-basis: 100%; order: 3; margin: 4px 0 0; padding: 8px; max-height: 220px; overflow: auto; font-size: 11.5px; color: var(--xf-ink); background: var(--xf-surface); border: 1px solid var(--xf-border); border-radius: 6px; white-space: pre; }
|
|
412
|
+
.xeplr-factory-publish.is-confirm { align-items: center; color: var(--xf-ink); background: color-mix(in srgb, var(--xf-warn) 10%, transparent); border-bottom-color: color-mix(in srgb, var(--xf-warn) 40%, transparent); }
|
|
319
413
|
.xeplr-factory-confirm-text { flex: 1; line-height: 1.5; }
|
|
320
414
|
.xeplr-factory-confirm-text ul { margin: 4px 0; padding-left: 18px; }
|
|
321
415
|
.xeplr-factory-confirm-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
package/src/designs/index.js
CHANGED
|
@@ -9,3 +9,7 @@ export { default as FlowBuilderSample } from './FlowBuilderSample.jsx'
|
|
|
9
9
|
export { default as ControlView, VIEWS } from './ControlView.jsx'
|
|
10
10
|
export { default as Palette, DRAG_TYPE } from './Palette.jsx'
|
|
11
11
|
export { default as PropertyPanel, EDITORS } from './PropertyPanel.jsx'
|
|
12
|
+
|
|
13
|
+
// The body of another package's "design this screen" window — see
|
|
14
|
+
// ../useScreenSource.js, which is how it is normally reached.
|
|
15
|
+
export { ScreenEditorSample } from './ScreenEditorSample.jsx'
|
package/src/designs/styles.js
CHANGED
|
@@ -40,12 +40,22 @@ export function boxStyle(style) {
|
|
|
40
40
|
})
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The screen's defaults, on its container: what every control inherits.
|
|
45
|
+
*
|
|
46
|
+
* A screen with no colours of its own follows the app's theme (factory.css).
|
|
47
|
+
* One that sets them keeps them EVERYWHERE — its ink and paper become the
|
|
48
|
+
* tokens the controls read, input boxes included — so a screen designed dark
|
|
49
|
+
* ink on white is never shown as that ink on a dark theme's input box.
|
|
50
|
+
*/
|
|
44
51
|
export function screenStyle(doc) {
|
|
45
52
|
const s = doc.style || {}
|
|
46
53
|
return clean({
|
|
47
54
|
'--xf-width': doc.width,
|
|
48
55
|
'--xf-font': s.fontSize,
|
|
56
|
+
'--xf-ink': s.color,
|
|
57
|
+
'--xf-surface': s.background,
|
|
58
|
+
'--xf-field-bg': s.background,
|
|
49
59
|
fontFamily: s.fontFamily,
|
|
50
60
|
color: s.color,
|
|
51
61
|
background: s.background
|
package/src/generate.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { CONTROLS, CHOICE_TYPES } from './controls.js'
|
|
27
27
|
import { createScreen, addControl, MARGIN, GAP, slugify, camelName, stepsOf } from './document.js'
|
|
28
28
|
import { assertValidDocument } from './validateDocument.js'
|
|
29
|
+
import { presetFor, presetProps, suggestPreset } from './presets.js'
|
|
29
30
|
|
|
30
31
|
/** Heights by control, as page fractions (a page is as tall as it is wide). */
|
|
31
32
|
export const SPEC_HEIGHTS = {
|
|
@@ -45,7 +46,7 @@ export const SPEC_HEIGHTS = {
|
|
|
45
46
|
*/
|
|
46
47
|
export const ROW_GAP = 0.04
|
|
47
48
|
|
|
48
|
-
const FIELD_KEYS = ['type', 'label', 'name', 'required', 'placeholder', 'default', 'validation', 'data', 'options', 'table', 'layout', 'accept', 'maxSize', 'step', 'steps', 'showNumbers', 'width', 'text', 'variant', 'style']
|
|
49
|
+
const FIELD_KEYS = ['type', 'preset', 'label', 'name', 'required', 'placeholder', 'default', 'validation', 'data', 'options', 'table', 'layout', 'accept', 'maxSize', 'step', 'steps', 'showNumbers', 'width', 'text', 'variant', 'style']
|
|
49
50
|
const SPEC_KEYS = ['name', 'id', 'source', 'columns', 'heading', 'fields', 'list', 'aspect', 'width', 'style']
|
|
50
51
|
const LIST_KEYS = ['title', 'source', 'columns', 'pageSize', 'actions', 'style']
|
|
51
52
|
|
|
@@ -58,6 +59,12 @@ const LIST_KEYS = ['title', 'source', 'columns', 'pageSize', 'actions', 'style']
|
|
|
58
59
|
* fields in reading order. Each: { label, type?='text', name?, required?, placeholder?,
|
|
59
60
|
* default?, validation?, width?: 'half'|'full' }
|
|
60
61
|
* dropdown: options: ['Full time', …] or [{ id, name }] —or— table: 'departments'
|
|
62
|
+
* READY-MADE: `type` may be a preset key — 'email', 'phone', 'url', 'linkedin',
|
|
63
|
+
* 'age', 'dateOfBirth', 'gender', 'country', 'amount', 'percentage', 'yesNo',
|
|
64
|
+
* 'postalCode' (presets.js) — or `preset: '<key>'`. A field with NO type and
|
|
65
|
+
* no options is matched by its label: { label: 'Age' } is a whole number 0–130,
|
|
66
|
+
* { label: 'LinkedIn URL' } a LinkedIn address. `type: 'text'` opts out.
|
|
67
|
+
* Anything the field also gives (label, required, validation…) wins.
|
|
61
68
|
* section heading: { type: 'label', text, variant?: 'subheading' }
|
|
62
69
|
* source? the table records are saved to, e.g. 'employees'
|
|
63
70
|
* width? design width in px (default 800) — style sizes are px at this width
|
|
@@ -65,7 +72,8 @@ const LIST_KEYS = ['title', 'source', 'columns', 'pageSize', 'actions', 'style']
|
|
|
65
72
|
* list? true, or { title, source, columns: [{ field, label }], pageSize, actions } —
|
|
66
73
|
* a list of the saved records below the fields, with New / Edit / Delete
|
|
67
74
|
*
|
|
68
|
-
*
|
|
75
|
+
* Do not add a button control for saving: every screen already has Save (and
|
|
76
|
+
* Cancel where it can go back) in its footer — an AJAX call, never a form submit.
|
|
69
77
|
*/
|
|
70
78
|
export function screenFromSpec(spec, controls = CONTROLS) {
|
|
71
79
|
if (!spec || typeof spec !== 'object') throw new Error('screenFromSpec: spec must be an object')
|
|
@@ -134,14 +142,19 @@ export function screenFromSpec(spec, controls = CONTROLS) {
|
|
|
134
142
|
const at = `spec.fields[${i}]`
|
|
135
143
|
if (!field || typeof field !== 'object') throw new Error(`screenFromSpec: ${at} must be an object`)
|
|
136
144
|
unknownKeys(field, FIELD_KEYS, at)
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
// A ready-made field: named (type or preset), or recognised from a label
|
|
146
|
+
// that says nothing else about itself.
|
|
147
|
+
const bare = !field.type && !field.preset && !field.options && !field.table && !field.data
|
|
148
|
+
const preset = presetFor(field.preset) || presetFor(field.type) || (bare ? suggestPreset(field.label) : null)
|
|
149
|
+
if (field.preset && !presetFor(field.preset)) throw new Error(`screenFromSpec: ${at}.preset "${field.preset}" is not a ready-made field`)
|
|
150
|
+
const type = preset ? preset.control : (field.type || 'text')
|
|
151
|
+
if (!controls[type]) throw new Error(`screenFromSpec: ${at}.type "${type}" is not a control or a ready-made field — controls: ${Object.keys(controls).join(', ')}`)
|
|
139
152
|
if (field.width !== undefined && field.width !== 'half' && field.width !== 'full') throw new Error(`screenFromSpec: ${at}.width must be "half" or "full"`)
|
|
140
153
|
if (field.step !== undefined) {
|
|
141
154
|
if (!stepper) throw new Error(`screenFromSpec: ${at}.step needs a stepper before it — { "type": "stepper", "steps": [...] }`)
|
|
142
155
|
openStep(field.step)
|
|
143
156
|
}
|
|
144
|
-
place(type, propsFromField(type, field, at), field.width)
|
|
157
|
+
place(type, preset ? withPreset(preset, propsFromField(type, { ...field, type }, at, true), field) : propsFromField(type, field, at), field.width)
|
|
145
158
|
})
|
|
146
159
|
|
|
147
160
|
if (col > 0) { y += rowH + ROW_GAP; col = 0; rowH = 0 }
|
|
@@ -157,7 +170,17 @@ export function screenFromSpec(spec, controls = CONTROLS) {
|
|
|
157
170
|
return assertValidDocument(doc, controls)
|
|
158
171
|
}
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
/** A preset's props under what the field itself gave. */
|
|
174
|
+
function withPreset(preset, own, field) {
|
|
175
|
+
const base = presetProps(preset)
|
|
176
|
+
const out = { ...base, ...own }
|
|
177
|
+
// Rules merge key by key: { label: 'Age', validation: { max: 120 } } keeps min and integer.
|
|
178
|
+
if (base.validation || own.validation) out.validation = { ...(base.validation || {}), ...(own.validation || {}) }
|
|
179
|
+
if (!field.label) out.label = base.label
|
|
180
|
+
return out
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function propsFromField(type, field, at, fromPreset) {
|
|
161
184
|
if (type === 'stepper') {
|
|
162
185
|
const steps = field.steps
|
|
163
186
|
if (!Array.isArray(steps) || steps.length < 2) throw new Error(`screenFromSpec: ${at} is a stepper — give it steps: ["Connect", "Transform", …]`)
|
|
@@ -175,13 +198,15 @@ function propsFromField(type, field, at) {
|
|
|
175
198
|
if (field.style) props.style = field.style
|
|
176
199
|
return props
|
|
177
200
|
}
|
|
178
|
-
if (!field.label) throw new Error(`screenFromSpec: ${at}.label is required`)
|
|
179
|
-
const props = { label: field.label }
|
|
201
|
+
if (!field.label && !fromPreset) throw new Error(`screenFromSpec: ${at}.label is required`)
|
|
202
|
+
const props = field.label ? { label: field.label } : {}
|
|
180
203
|
;['name', 'required', 'placeholder', 'default', 'validation', 'style'].forEach((k) => {
|
|
181
204
|
if (field[k] !== undefined) props[k] = field[k]
|
|
182
205
|
})
|
|
183
|
-
if (type === 'dropdown' && props.placeholder === undefined) props.placeholder = 'Select…'
|
|
184
|
-
if (CHOICE_TYPES.includes(type)) {
|
|
206
|
+
if (type === 'dropdown' && props.placeholder === undefined && !fromPreset) props.placeholder = 'Select…'
|
|
207
|
+
if (CHOICE_TYPES.includes(type) && fromPreset && !field.options && !field.table && !field.data) {
|
|
208
|
+
if (field.layout !== undefined && type !== 'dropdown') props.layout = field.layout
|
|
209
|
+
} else if (CHOICE_TYPES.includes(type)) {
|
|
185
210
|
props.data = dataFromField(field, at, type)
|
|
186
211
|
if (field.layout !== undefined && type !== 'dropdown') props.layout = field.layout
|
|
187
212
|
// A field reading another table stores that row's id: name the column
|
package/src/hooks.js
CHANGED
|
@@ -40,7 +40,7 @@ export class FactoryHooks {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Save — called
|
|
43
|
+
* Save — called when the person presses Save (or a flow's Next with unsaved changes).
|
|
44
44
|
* @param values what the form holds
|
|
45
45
|
* @param ctx.steps switch steps off or on as the record decides them —
|
|
46
46
|
* ctx.steps.disable('approval'), ctx.steps.enable()
|
package/src/index.js
CHANGED
|
@@ -10,12 +10,16 @@ export * from './model.js'
|
|
|
10
10
|
|
|
11
11
|
// Controllers
|
|
12
12
|
export { useFactoryBuilder } from './useFactoryBuilder.js'
|
|
13
|
-
export { useFactoryScreen, normaliseOptions
|
|
13
|
+
export { useFactoryScreen, normaliseOptions } from './useFactoryScreen.js'
|
|
14
|
+
// This app's screens, shaped for another package's designer — see
|
|
15
|
+
// useScreenSource.js. Two props instead of a page of glue per application.
|
|
16
|
+
export { useScreenSource } from './useScreenSource.js'
|
|
17
|
+
export { describeScreen, screenStub, loadScreens, keyFromName, editScreenOf } from './screenSource.js'
|
|
14
18
|
export { useFlowRun } from './useFlowRun.js'
|
|
15
19
|
export { useFlowBuilder, FLOW_AUTOSAVE_DELAY } from './useFlowBuilder.js'
|
|
16
20
|
|
|
17
21
|
// Designs
|
|
18
|
-
export { BuilderSample, ScreenSample, ListView, ScreenModal, FlowRunnerSample, FlowBuilderSample, ControlView, VIEWS, Palette, DRAG_TYPE, PropertyPanel, EDITORS } from './designs/index.js'
|
|
22
|
+
export { BuilderSample, ScreenSample, ListView, ScreenModal, FlowRunnerSample, FlowBuilderSample, ScreenEditorSample, ControlView, VIEWS, Palette, DRAG_TYPE, PropertyPanel, EDITORS } from './designs/index.js'
|
|
19
23
|
|
|
20
24
|
// Front-end hooks — extend FactoryHooks, override what you need, call super
|
|
21
25
|
export { FactoryHooks } from './hooks.js'
|
package/src/model.js
CHANGED
|
@@ -19,8 +19,9 @@ export { createFactoryApi } from './remote.js'
|
|
|
19
19
|
export {
|
|
20
20
|
STANDARD_COLUMNS, RESERVED_COLUMNS, MAX_IDENTIFIER,
|
|
21
21
|
columnForField, tableForScreen, columnSql, widening, diffTables, migrationFor, nextMigrationName,
|
|
22
|
-
createStatements, addStatements, alterStatement, dropStatement, columnFromDatabase, planTableChange
|
|
22
|
+
createStatements, addStatements, alterStatement, dropStatement, columnFromDatabase, planTableChange, conversionFor
|
|
23
23
|
} from './tableSchema.js'
|
|
24
|
+
export { FIELD_PRESETS, PATTERNS, INPUT_TYPES, presetFor, presetProps, suggestPreset, presetGroup, presetHints, convertField } from './presets.js'
|
|
24
25
|
export { createFlowsApi } from './flows.js'
|
|
25
26
|
export {
|
|
26
27
|
FLOW_KIND, FLOW_VERSION, FLOW_END, FLOW_OPERATORS, VALUELESS_OPERATORS,
|