@shamar/core 0.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/dist/actions.d.ts +39 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +97 -0
- package/dist/actions.js.map +1 -0
- package/dist/alignment.d.ts +13 -0
- package/dist/alignment.d.ts.map +1 -0
- package/dist/alignment.js +34 -0
- package/dist/alignment.js.map +1 -0
- package/dist/currency.d.ts +16 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +52 -0
- package/dist/currency.js.map +1 -0
- package/dist/form.d.ts +281 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +756 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/infolist.d.ts +90 -0
- package/dist/infolist.d.ts.map +1 -0
- package/dist/infolist.js +273 -0
- package/dist/infolist.js.map +1 -0
- package/dist/labels.d.ts +6 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +26 -0
- package/dist/labels.js.map +1 -0
- package/dist/panel.d.ts +20 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +46 -0
- package/dist/panel.js.map +1 -0
- package/dist/permissions.d.ts +43 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +152 -0
- package/dist/permissions.js.map +1 -0
- package/dist/policy-types.d.ts +23 -0
- package/dist/policy-types.d.ts.map +1 -0
- package/dist/policy-types.js +8 -0
- package/dist/policy-types.js.map +1 -0
- package/dist/reactivity.d.ts +23 -0
- package/dist/reactivity.d.ts.map +1 -0
- package/dist/reactivity.js +68 -0
- package/dist/reactivity.js.map +1 -0
- package/dist/record-value.d.ts +31 -0
- package/dist/record-value.d.ts.map +1 -0
- package/dist/record-value.js +118 -0
- package/dist/record-value.js.map +1 -0
- package/dist/registry.d.ts +16 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +49 -0
- package/dist/registry.js.map +1 -0
- package/dist/relation.d.ts +28 -0
- package/dist/relation.d.ts.map +1 -0
- package/dist/relation.js +69 -0
- package/dist/relation.js.map +1 -0
- package/dist/resource.d.ts +100 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +165 -0
- package/dist/resource.js.map +1 -0
- package/dist/schemas.d.ts +193 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +501 -0
- package/dist/schemas.js.map +1 -0
- package/dist/table.d.ts +71 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +172 -0
- package/dist/table.js.map +1 -0
- package/dist/types.d.ts +474 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +27 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +182 -0
- package/dist/validation.js.map +1 -0
- package/package.json +30 -0
package/dist/form.js
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { buildSchemaNodes, collectFields, } from './schemas.js';
|
|
2
|
+
import { currencySymbol, normalizeCurrencyOptions, } from './currency.js';
|
|
3
|
+
import { applyRelationship, } from './relation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Base for form field components (`TextInput::make()` style).
|
|
6
|
+
* Field chains only expose field modifiers — never sibling factories.
|
|
7
|
+
*/
|
|
8
|
+
export class FormComponent {
|
|
9
|
+
/** Shared schema leaf marker (Filament 5 schemas). */
|
|
10
|
+
_schemaLeaf = 'field';
|
|
11
|
+
config;
|
|
12
|
+
constructor(name, type) {
|
|
13
|
+
this.config = { name, type };
|
|
14
|
+
}
|
|
15
|
+
/** Apply Filament-style relationship binding. */
|
|
16
|
+
applyRelationship(resource, titleAttribute, options) {
|
|
17
|
+
applyRelationship(this.config, resource, titleAttribute, options);
|
|
18
|
+
}
|
|
19
|
+
setCreateOption(value) {
|
|
20
|
+
if (!this.config.relation) {
|
|
21
|
+
throw new Error(`Call .relationship() before .createOption() on field "${this.config.name}"`);
|
|
22
|
+
}
|
|
23
|
+
this.config.relation.createOption = value;
|
|
24
|
+
}
|
|
25
|
+
setCreateAndEditOption(value) {
|
|
26
|
+
if (!this.config.relation) {
|
|
27
|
+
throw new Error(`Call .relationship() before .createAndEditOption() on field "${this.config.name}"`);
|
|
28
|
+
}
|
|
29
|
+
this.config.relation.createAndEditOption = value;
|
|
30
|
+
}
|
|
31
|
+
setRelationWidget(widget) {
|
|
32
|
+
if (!this.config.relation) {
|
|
33
|
+
throw new Error(`Call .relationship() before .widget() on field "${this.config.name}"`);
|
|
34
|
+
}
|
|
35
|
+
this.config.relation.widget = widget;
|
|
36
|
+
if (widget === 'radio') {
|
|
37
|
+
this.config.type = 'radio';
|
|
38
|
+
}
|
|
39
|
+
else if (widget === 'checkboxList') {
|
|
40
|
+
this.config.type = 'checkboxList';
|
|
41
|
+
this.config.multiple = true;
|
|
42
|
+
}
|
|
43
|
+
else if (widget === 'table') {
|
|
44
|
+
this.config.type = 'relationTable';
|
|
45
|
+
this.config.multiple = true;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.config.type = 'relation';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
label(value) {
|
|
52
|
+
this.config.label = value;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
required(value = true) {
|
|
56
|
+
this.config.required = value;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
searchable(value = true) {
|
|
60
|
+
this.config.searchable = value;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
placeholder(value) {
|
|
64
|
+
this.config.placeholder = value;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/** Helper text below the field. Alias: `helperText()`. */
|
|
68
|
+
help(value) {
|
|
69
|
+
this.config.help = value;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/** Filament-style alias for `help()`. */
|
|
73
|
+
helperText(value) {
|
|
74
|
+
return this.help(value);
|
|
75
|
+
}
|
|
76
|
+
/** Short text beside the label. */
|
|
77
|
+
hint(value) {
|
|
78
|
+
this.config.hint = value;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
hiddenOnForm(value = true) {
|
|
82
|
+
this.config.hiddenOnForm = value;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
hiddenOnDetail(value = true) {
|
|
86
|
+
this.config.hiddenOnDetail = value;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
readonly(value = true) {
|
|
90
|
+
this.config.readonly = value;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
disabled(value = true) {
|
|
94
|
+
this.config.disabled = value;
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
visible(value = true) {
|
|
98
|
+
this.config.visible = value;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Hide when true (inverse of {@link visible}).
|
|
103
|
+
* Closures receive the same FieldContext as `visible` / `required`.
|
|
104
|
+
*/
|
|
105
|
+
hidden(value = true) {
|
|
106
|
+
if (typeof value === 'function') {
|
|
107
|
+
const predicate = value;
|
|
108
|
+
this.config.visible = (ctx) => !predicate(ctx);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.config.visible = !value;
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
dehydrated(value = true) {
|
|
116
|
+
this.config.dehydrated = value;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
default(value) {
|
|
120
|
+
this.config.default = value;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
createOnly(value = true) {
|
|
124
|
+
this.config.createOnly = value;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
columnSpan(value) {
|
|
128
|
+
this.config.columnSpan = value;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
colspan(value) {
|
|
132
|
+
return this.columnSpan(value);
|
|
133
|
+
}
|
|
134
|
+
columnSpanFull() {
|
|
135
|
+
this.config.columnSpan = 'full';
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
colspanFull() {
|
|
139
|
+
return this.columnSpanFull();
|
|
140
|
+
}
|
|
141
|
+
columnStart(value) {
|
|
142
|
+
this.config.columnStart = value;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
/** Filament-style horizontal alignment of the field control. */
|
|
146
|
+
alignment(value) {
|
|
147
|
+
this.config.alignment = value;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
alignStart() {
|
|
151
|
+
return this.alignment('start');
|
|
152
|
+
}
|
|
153
|
+
alignCenter() {
|
|
154
|
+
return this.alignment('center');
|
|
155
|
+
}
|
|
156
|
+
alignEnd() {
|
|
157
|
+
return this.alignment('end');
|
|
158
|
+
}
|
|
159
|
+
alignJustify() {
|
|
160
|
+
return this.alignment('justify');
|
|
161
|
+
}
|
|
162
|
+
alignLeft() {
|
|
163
|
+
return this.alignment('left');
|
|
164
|
+
}
|
|
165
|
+
alignRight() {
|
|
166
|
+
return this.alignment('right');
|
|
167
|
+
}
|
|
168
|
+
inline(value = true) {
|
|
169
|
+
this.config.inline = value;
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
rows(value) {
|
|
173
|
+
this.config.rows = value;
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
prefix(value) {
|
|
177
|
+
this.config.prefix = value;
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
suffix(value) {
|
|
181
|
+
this.config.suffix = value;
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
/** Filament-style: prefix inside (default) or outside the input border. */
|
|
185
|
+
inlinePrefix(value = true) {
|
|
186
|
+
this.config.inlinePrefix = value;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
/** Filament-style: suffix inside (default) or outside the input border. */
|
|
190
|
+
inlineSuffix(value = true) {
|
|
191
|
+
this.config.inlineSuffix = value;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/** Set both prefix and suffix inline mode. */
|
|
195
|
+
inlineAffixes(value = true) {
|
|
196
|
+
this.config.inlinePrefix = value;
|
|
197
|
+
this.config.inlineSuffix = value;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
prefixIcon(value) {
|
|
201
|
+
this.config.prefixIcon = value;
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
suffixIcon(value) {
|
|
205
|
+
this.config.suffixIcon = value;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
autocomplete(value) {
|
|
209
|
+
this.config.autocomplete = value;
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
inputMode(value) {
|
|
213
|
+
this.config.inputMode = value;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
/** Exact string length (also sets minLength + maxLength). */
|
|
217
|
+
length(value) {
|
|
218
|
+
this.config.length = value;
|
|
219
|
+
this.config.minLength = value;
|
|
220
|
+
this.config.maxLength = value;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
minLength(value) {
|
|
224
|
+
this.config.minLength = value;
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
maxLength(value) {
|
|
228
|
+
this.config.maxLength = value;
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
minValue(value) {
|
|
232
|
+
this.config.minValue = value;
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
/** Filament alias for numeric/date lower bound. */
|
|
236
|
+
min(value) {
|
|
237
|
+
return this.minValue(value);
|
|
238
|
+
}
|
|
239
|
+
maxValue(value) {
|
|
240
|
+
this.config.maxValue = value;
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
/** Filament alias for numeric/date upper bound. */
|
|
244
|
+
max(value) {
|
|
245
|
+
return this.maxValue(value);
|
|
246
|
+
}
|
|
247
|
+
step(value) {
|
|
248
|
+
this.config.step = value;
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
/** HTML `pattern` + server-side RegExp check. */
|
|
252
|
+
pattern(value) {
|
|
253
|
+
this.config.pattern = value;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/** Alias for `pattern()`. */
|
|
257
|
+
regex(value) {
|
|
258
|
+
return this.pattern(value);
|
|
259
|
+
}
|
|
260
|
+
copyable(value = true) {
|
|
261
|
+
this.config.copyable = value;
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
datalist(values) {
|
|
265
|
+
this.config.datalist = [...values];
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
extraInputAttributes(attrs, options) {
|
|
269
|
+
this.config.extraInputAttributes = options?.merge
|
|
270
|
+
? { ...this.config.extraInputAttributes, ...attrs }
|
|
271
|
+
: { ...attrs };
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Re-evaluate form state while typing (Filament-style).
|
|
276
|
+
*/
|
|
277
|
+
live(mode = true) {
|
|
278
|
+
this.config.live = mode;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
afterStateUpdated(callback) {
|
|
282
|
+
this.config.afterStateUpdated = callback;
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
unique(options = true) {
|
|
286
|
+
this.config.unique = options;
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
/** @internal */
|
|
290
|
+
build() {
|
|
291
|
+
return { ...this.config };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Filament `TextInput::make()`. */
|
|
295
|
+
export class TextInput extends FormComponent {
|
|
296
|
+
static make(name) {
|
|
297
|
+
return new TextInput(name);
|
|
298
|
+
}
|
|
299
|
+
constructor(name) {
|
|
300
|
+
super(name, 'text');
|
|
301
|
+
}
|
|
302
|
+
email() {
|
|
303
|
+
this.config.type = 'email';
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
password() {
|
|
307
|
+
this.config.type = 'password';
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
/** Filament `revealable()` — show/hide password toggle. */
|
|
311
|
+
revealable(value = true) {
|
|
312
|
+
this.config.revealable = value;
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
tel() {
|
|
316
|
+
this.config.type = 'tel';
|
|
317
|
+
this.config.inputMode = this.config.inputMode ?? 'tel';
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
url() {
|
|
321
|
+
this.config.type = 'url';
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
numeric() {
|
|
325
|
+
this.config.type = 'number';
|
|
326
|
+
this.config.inputMode = this.config.inputMode ?? 'decimal';
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
integer() {
|
|
330
|
+
this.config.type = 'number';
|
|
331
|
+
this.config.step = this.config.step ?? 1;
|
|
332
|
+
this.config.inputMode = this.config.inputMode ?? 'numeric';
|
|
333
|
+
return this;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Format a numeric field as currency (display via Intl; value stays numeric).
|
|
337
|
+
* Sets `numeric()` and a currency-symbol prefix when none is set.
|
|
338
|
+
*/
|
|
339
|
+
currency(codeOrOptions = 'USD', options) {
|
|
340
|
+
this.numeric();
|
|
341
|
+
const currency = normalizeCurrencyOptions(codeOrOptions, options);
|
|
342
|
+
this.config.currency = currency;
|
|
343
|
+
this.config.step = this.config.step ?? (currency.precision === 0 ? 1 : 0.01);
|
|
344
|
+
if (this.config.prefix == null && this.config.prefixIcon == null) {
|
|
345
|
+
this.config.prefix = currencySymbol(currency.code, currency.locale);
|
|
346
|
+
}
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Filament `Textarea::make()`. */
|
|
351
|
+
export class Textarea extends FormComponent {
|
|
352
|
+
static make(name) {
|
|
353
|
+
return new Textarea(name);
|
|
354
|
+
}
|
|
355
|
+
constructor(name) {
|
|
356
|
+
super(name, 'textarea');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Filament `Select::make()`. */
|
|
360
|
+
export class Select extends FormComponent {
|
|
361
|
+
static make(name) {
|
|
362
|
+
return new Select(name);
|
|
363
|
+
}
|
|
364
|
+
constructor(name) {
|
|
365
|
+
super(name, 'select');
|
|
366
|
+
this.config.options = [];
|
|
367
|
+
}
|
|
368
|
+
options(entries) {
|
|
369
|
+
this.config.options = entries;
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
multiple(value = true) {
|
|
373
|
+
this.config.multiple = value;
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* When `false`, omit the blank placeholder option (Filament `selectablePlaceholder(false)`).
|
|
378
|
+
*/
|
|
379
|
+
selectablePlaceholder(value = true) {
|
|
380
|
+
this.config.selectablePlaceholder = value;
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Use a native `<select>` instead of the Shamar combobox.
|
|
385
|
+
* Useful for simple forms or when custom OS select behaviour is preferred.
|
|
386
|
+
*/
|
|
387
|
+
native(value = true) {
|
|
388
|
+
this.config.nativeSelect = value;
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Filament-style relationship binding.
|
|
393
|
+
* @example Select.make('companyId').relationship('companies', 'name')
|
|
394
|
+
* @example Select.make('categoryIds').relationship('categories', 'name', { kind: 'manyToMany' }).multiple()
|
|
395
|
+
*/
|
|
396
|
+
relationship(resource, titleAttribute, options) {
|
|
397
|
+
this.applyRelationship(resource, titleAttribute, options);
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
/** Presentation widget (`combobox` default for BelongsTo / M2M). */
|
|
401
|
+
widget(value) {
|
|
402
|
+
this.setRelationWidget(value);
|
|
403
|
+
return this;
|
|
404
|
+
}
|
|
405
|
+
/** Use radio buttons for a BelongsTo relationship. */
|
|
406
|
+
radio() {
|
|
407
|
+
return this.widget('radio');
|
|
408
|
+
}
|
|
409
|
+
/** Enable inline Create from the search query (quick-create). */
|
|
410
|
+
createOption(value = true) {
|
|
411
|
+
this.setCreateOption(value);
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
/** Enable Create & Edit (open related create dialog, then pick). */
|
|
415
|
+
createAndEditOption(value = true) {
|
|
416
|
+
this.setCreateAndEditOption(value);
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/** Filament `Toggle::make()` — switch UI. */
|
|
421
|
+
export class Toggle extends FormComponent {
|
|
422
|
+
static make(name) {
|
|
423
|
+
return new Toggle(name);
|
|
424
|
+
}
|
|
425
|
+
constructor(name) {
|
|
426
|
+
super(name, 'boolean');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/** Filament `Checkbox::make()` — native checkbox (distinct from Toggle). */
|
|
430
|
+
export class Checkbox extends FormComponent {
|
|
431
|
+
static make(name) {
|
|
432
|
+
return new Checkbox(name);
|
|
433
|
+
}
|
|
434
|
+
constructor(name) {
|
|
435
|
+
super(name, 'checkbox');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/** Filament `Radio::make()`. */
|
|
439
|
+
export class Radio extends FormComponent {
|
|
440
|
+
static make(name) {
|
|
441
|
+
return new Radio(name);
|
|
442
|
+
}
|
|
443
|
+
constructor(name) {
|
|
444
|
+
super(name, 'radio');
|
|
445
|
+
this.config.options = [];
|
|
446
|
+
}
|
|
447
|
+
options(entries) {
|
|
448
|
+
this.config.options = entries;
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
relationship(resource, titleAttribute, options) {
|
|
452
|
+
this.applyRelationship(resource, titleAttribute, {
|
|
453
|
+
...options,
|
|
454
|
+
kind: options?.kind ?? 'belongsTo',
|
|
455
|
+
widget: options?.widget ?? 'radio',
|
|
456
|
+
});
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
createOption(value = true) {
|
|
460
|
+
this.setCreateOption(value);
|
|
461
|
+
return this;
|
|
462
|
+
}
|
|
463
|
+
createAndEditOption(value = true) {
|
|
464
|
+
this.setCreateAndEditOption(value);
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/** Multi-option checkbox list (static options or manyToMany relationship). */
|
|
469
|
+
export class CheckboxList extends FormComponent {
|
|
470
|
+
static make(name) {
|
|
471
|
+
return new CheckboxList(name);
|
|
472
|
+
}
|
|
473
|
+
constructor(name) {
|
|
474
|
+
super(name, 'checkboxList');
|
|
475
|
+
this.config.options = [];
|
|
476
|
+
this.config.multiple = true;
|
|
477
|
+
}
|
|
478
|
+
options(entries) {
|
|
479
|
+
this.config.options = entries;
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
relationship(resource, titleAttribute, options) {
|
|
483
|
+
this.applyRelationship(resource, titleAttribute, {
|
|
484
|
+
...options,
|
|
485
|
+
kind: options?.kind ?? 'manyToMany',
|
|
486
|
+
widget: options?.widget ?? 'checkboxList',
|
|
487
|
+
});
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
createOption(value = true) {
|
|
491
|
+
this.setCreateOption(value);
|
|
492
|
+
return this;
|
|
493
|
+
}
|
|
494
|
+
createAndEditOption(value = true) {
|
|
495
|
+
this.setCreateAndEditOption(value);
|
|
496
|
+
return this;
|
|
497
|
+
}
|
|
498
|
+
checkboxColumns(count) {
|
|
499
|
+
this.config.checkboxColumns = count;
|
|
500
|
+
if (this.config.relation)
|
|
501
|
+
this.config.relation.checkboxColumns = count;
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
checkboxFramed(value = true) {
|
|
505
|
+
this.config.checkboxFramed = value;
|
|
506
|
+
if (this.config.relation)
|
|
507
|
+
this.config.relation.checkboxFramed = value;
|
|
508
|
+
return this;
|
|
509
|
+
}
|
|
510
|
+
cascadeWildcards(value = true) {
|
|
511
|
+
this.config.cascadeWildcards = value;
|
|
512
|
+
if (this.config.relation)
|
|
513
|
+
this.config.relation.cascadeWildcards = value;
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
groupBy(field) {
|
|
517
|
+
this.config.groupBy = field;
|
|
518
|
+
if (this.config.relation)
|
|
519
|
+
this.config.relation.groupBy = field;
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Loom-style permission assignment for Role forms (ORM-agnostic).
|
|
525
|
+
*
|
|
526
|
+
* Requires a registered `permissions` resource whose records expose
|
|
527
|
+
* `name`, `resource`, `ability`, and `label`. Adapters enrich relation
|
|
528
|
+
* search with `name` / `group` / `ability` for grouping and wildcard cascade.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* PermissionsAssignment.make('permissionIds')
|
|
532
|
+
* PermissionsAssignment.make('permissionIds').relationship('perms', 'name')
|
|
533
|
+
*/
|
|
534
|
+
export class PermissionsAssignment extends CheckboxList {
|
|
535
|
+
static make(name = 'permissionIds') {
|
|
536
|
+
return new PermissionsAssignment(name);
|
|
537
|
+
}
|
|
538
|
+
constructor(name) {
|
|
539
|
+
super(name);
|
|
540
|
+
this.label('Permissions')
|
|
541
|
+
.relationship('permissions', 'label')
|
|
542
|
+
.checkboxColumns(4)
|
|
543
|
+
.groupBy('resource')
|
|
544
|
+
.cascadeWildcards()
|
|
545
|
+
.checkboxFramed(false)
|
|
546
|
+
.columnSpanFull();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Catalog-backed ability picker — same UI as {@link PermissionsAssignment},
|
|
551
|
+
* but dehydrates permission **names** (`products:view`, `*`) instead of ids.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* AbilitiesAssignment.make('abilities')
|
|
555
|
+
*/
|
|
556
|
+
export class AbilitiesAssignment extends CheckboxList {
|
|
557
|
+
static make(name = 'abilities') {
|
|
558
|
+
return new AbilitiesAssignment(name);
|
|
559
|
+
}
|
|
560
|
+
constructor(name) {
|
|
561
|
+
super(name);
|
|
562
|
+
this.label('Abilities')
|
|
563
|
+
.relationship('permissions', 'label', { valueAttribute: 'name' })
|
|
564
|
+
.checkboxColumns(4)
|
|
565
|
+
.groupBy('resource')
|
|
566
|
+
.cascadeWildcards()
|
|
567
|
+
.checkboxFramed(false)
|
|
568
|
+
.columnSpanFull();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Inline relationship table for HasMany / ManyToMany (attach, create, create & edit).
|
|
573
|
+
* HasMany fields are not dehydrated — children own the foreign key.
|
|
574
|
+
*/
|
|
575
|
+
export class RelationTable extends FormComponent {
|
|
576
|
+
static make(name) {
|
|
577
|
+
return new RelationTable(name);
|
|
578
|
+
}
|
|
579
|
+
constructor(name) {
|
|
580
|
+
super(name, 'relationTable');
|
|
581
|
+
this.config.multiple = true;
|
|
582
|
+
this.config.dehydrated = false;
|
|
583
|
+
}
|
|
584
|
+
relationship(resource, titleAttribute, options) {
|
|
585
|
+
this.applyRelationship(resource, titleAttribute, {
|
|
586
|
+
...options,
|
|
587
|
+
kind: options?.kind ?? 'hasMany',
|
|
588
|
+
widget: options?.widget ?? 'table',
|
|
589
|
+
});
|
|
590
|
+
return this;
|
|
591
|
+
}
|
|
592
|
+
createOption(value = true) {
|
|
593
|
+
this.setCreateOption(value);
|
|
594
|
+
return this;
|
|
595
|
+
}
|
|
596
|
+
createAndEditOption(value = true) {
|
|
597
|
+
this.setCreateAndEditOption(value);
|
|
598
|
+
return this;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/** Filament `Hidden::make()`. */
|
|
602
|
+
export class Hidden extends FormComponent {
|
|
603
|
+
static make(name) {
|
|
604
|
+
return new Hidden(name);
|
|
605
|
+
}
|
|
606
|
+
constructor(name) {
|
|
607
|
+
super(name, 'hidden');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/** Filament `ColorPicker::make()`. */
|
|
611
|
+
export class ColorPicker extends FormComponent {
|
|
612
|
+
static make(name) {
|
|
613
|
+
return new ColorPicker(name);
|
|
614
|
+
}
|
|
615
|
+
constructor(name) {
|
|
616
|
+
super(name, 'color');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/** Filament `TagsInput::make()` — state is `string[]`. */
|
|
620
|
+
export class TagsInput extends FormComponent {
|
|
621
|
+
static make(name) {
|
|
622
|
+
return new TagsInput(name);
|
|
623
|
+
}
|
|
624
|
+
constructor(name) {
|
|
625
|
+
super(name, 'tags');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/** Filament `DatePicker::make()`. */
|
|
629
|
+
export class DatePicker extends FormComponent {
|
|
630
|
+
static make(name) {
|
|
631
|
+
return new DatePicker(name);
|
|
632
|
+
}
|
|
633
|
+
constructor(name) {
|
|
634
|
+
super(name, 'date');
|
|
635
|
+
}
|
|
636
|
+
minDate(value) {
|
|
637
|
+
return this.minValue(value);
|
|
638
|
+
}
|
|
639
|
+
maxDate(value) {
|
|
640
|
+
return this.maxValue(value);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/** Filament `DateTimePicker::make()`. */
|
|
644
|
+
export class DateTimePicker extends FormComponent {
|
|
645
|
+
static make(name) {
|
|
646
|
+
return new DateTimePicker(name);
|
|
647
|
+
}
|
|
648
|
+
constructor(name) {
|
|
649
|
+
super(name, 'datetime');
|
|
650
|
+
}
|
|
651
|
+
minDate(value) {
|
|
652
|
+
return this.minValue(value);
|
|
653
|
+
}
|
|
654
|
+
maxDate(value) {
|
|
655
|
+
return this.maxValue(value);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/** Filament `FileUpload::make()`. */
|
|
659
|
+
export class FileUpload extends FormComponent {
|
|
660
|
+
static make(name) {
|
|
661
|
+
return new FileUpload(name);
|
|
662
|
+
}
|
|
663
|
+
constructor(name) {
|
|
664
|
+
super(name, 'file');
|
|
665
|
+
}
|
|
666
|
+
image() {
|
|
667
|
+
this.config.type = 'image';
|
|
668
|
+
this.config.accept = this.config.accept ?? 'image/*';
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
671
|
+
accept(value) {
|
|
672
|
+
this.config.accept = value;
|
|
673
|
+
return this;
|
|
674
|
+
}
|
|
675
|
+
multiple(value = true) {
|
|
676
|
+
this.config.multiple = value;
|
|
677
|
+
return this;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Form root — Filament `$form->columns(2)->schema([...])`.
|
|
682
|
+
*/
|
|
683
|
+
export class FormBuilder {
|
|
684
|
+
rootColumns = 2;
|
|
685
|
+
children = [];
|
|
686
|
+
columns(value) {
|
|
687
|
+
this.rootColumns = value;
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
schema(children) {
|
|
691
|
+
this.children = [...children];
|
|
692
|
+
return this;
|
|
693
|
+
}
|
|
694
|
+
build() {
|
|
695
|
+
let schema = buildSchemaNodes(this.children);
|
|
696
|
+
// Wrap bare fields in a plain container when only leaves at root.
|
|
697
|
+
const onlyLeaves = schema.length > 0 && schema.every((n) => n.kind === 'field' || n.kind === 'entry');
|
|
698
|
+
if (onlyLeaves || schema.length === 0) {
|
|
699
|
+
schema = [
|
|
700
|
+
{
|
|
701
|
+
kind: 'plain',
|
|
702
|
+
name: '_fields',
|
|
703
|
+
title: '',
|
|
704
|
+
columns: this.rootColumns,
|
|
705
|
+
children: schema.filter((n) => n.kind === 'field'),
|
|
706
|
+
},
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
const fields = collectFields(schema);
|
|
710
|
+
const sections = schema.map((node) => {
|
|
711
|
+
if (node.kind === 'section' || node.kind === 'fieldset' || node.kind === 'plain') {
|
|
712
|
+
return {
|
|
713
|
+
name: node.name ?? 'section',
|
|
714
|
+
title: node.title ?? '',
|
|
715
|
+
description: node.description,
|
|
716
|
+
icon: node.icon,
|
|
717
|
+
kind: node.kind === 'fieldset' ? 'fieldset' : node.kind === 'plain' ? 'plain' : 'section',
|
|
718
|
+
card: node.card,
|
|
719
|
+
columns: node.columns,
|
|
720
|
+
fields: collectFields([node]),
|
|
721
|
+
collapsible: node.collapsible,
|
|
722
|
+
collapsed: node.collapsed,
|
|
723
|
+
dense: node.dense,
|
|
724
|
+
gap: node.gap,
|
|
725
|
+
extraAttributes: node.extraAttributes,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
// Non-section root layouts still expose a synthetic section for BC consumers.
|
|
729
|
+
return {
|
|
730
|
+
name: node.name ?? 'layout',
|
|
731
|
+
title: node.title ?? '',
|
|
732
|
+
description: node.description,
|
|
733
|
+
icon: node.icon,
|
|
734
|
+
kind: 'section',
|
|
735
|
+
card: node.card,
|
|
736
|
+
columns: node.columns ?? this.rootColumns,
|
|
737
|
+
fields: collectFields([node]),
|
|
738
|
+
collapsible: node.collapsible,
|
|
739
|
+
collapsed: node.collapsed,
|
|
740
|
+
dense: node.dense,
|
|
741
|
+
gap: node.gap,
|
|
742
|
+
extraAttributes: node.extraAttributes,
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
return { schema, sections, fields };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
export function form(callback) {
|
|
749
|
+
const builder = new FormBuilder();
|
|
750
|
+
callback(builder);
|
|
751
|
+
return builder.build();
|
|
752
|
+
}
|
|
753
|
+
export function fieldTypeLabel(type) {
|
|
754
|
+
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
755
|
+
}
|
|
756
|
+
//# sourceMappingURL=form.js.map
|