@prolibu-suite/cobalt-form 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/cjs/co-form-wizard.cjs.entry.js +307 -0
  2. package/dist/cjs/co-form.cjs.entry.js +2 -1
  3. package/dist/cjs/cobalt-form.cjs.js +2 -2
  4. package/dist/cjs/index-CLL7Ervz.js +8155 -0
  5. package/dist/cjs/{index-8raPCV5a.js → index-Mpsm3UE7.js} +8 -0
  6. package/dist/cjs/index.cjs.js +151 -8869
  7. package/dist/cjs/loader.cjs.js +2 -2
  8. package/dist/cobalt-form/cobalt-form.esm.js +1 -1
  9. package/dist/cobalt-form/index.esm.js +1 -7
  10. package/dist/cobalt-form/p-2168be6b.entry.js +1 -0
  11. package/dist/cobalt-form/p-7b8f67bc.entry.js +1 -0
  12. package/dist/cobalt-form/p-C1670_IO.js +2 -0
  13. package/dist/cobalt-form/p-CPE0t-C2.js +7 -0
  14. package/dist/collection/collection-manifest.json +2 -1
  15. package/dist/collection/components/co-form/co-form.css +167 -1
  16. package/dist/collection/components/co-form/co-form.js +278 -9
  17. package/dist/collection/components/co-form-wizard/co-form-wizard.css +363 -0
  18. package/dist/collection/components/co-form-wizard/co-form-wizard.js +502 -0
  19. package/dist/components/co-form-wizard.d.ts +11 -0
  20. package/dist/components/co-form-wizard.js +1 -0
  21. package/dist/components/index.js +5 -5
  22. package/dist/esm/co-form-wizard.entry.js +305 -0
  23. package/dist/esm/co-form.entry.js +2 -1
  24. package/dist/esm/cobalt-form.js +3 -3
  25. package/dist/esm/{index-X0Keifac.js → index-C1670_IO.js} +8 -1
  26. package/dist/esm/index-CPE0t-C2.js +8144 -0
  27. package/dist/esm/index.js +147 -8865
  28. package/dist/esm/loader.js +3 -3
  29. package/dist/types/components/co-form/co-form.d.ts +53 -1
  30. package/dist/types/components/co-form-wizard/co-form-wizard.d.ts +118 -0
  31. package/dist/types/components.d.ts +184 -2
  32. package/package.json +6 -3
  33. package/dist/cobalt-form/p-X0Keifac.js +0 -2
  34. package/dist/cobalt-form/p-ef70e055.entry.js +0 -1
@@ -1,6 +1,14 @@
1
- import { h } from "@stencil/core";
2
- import { createForm, createInlineValidator, describeFields, normalizeForValidation, } from "@prolibu-suite/cobalt-form-core";
1
+ import { h, Host } from "@stencil/core";
2
+ import { createForm, createInlineValidator, describeFields, normalizeForValidation, resolveTheme, themeToStyles, } from "@prolibu-suite/cobalt-form-core";
3
3
  const PAGE_SIZE = 20;
4
+ function safeParse(json) {
5
+ try {
6
+ return JSON.parse(json);
7
+ }
8
+ catch (_a) {
9
+ return null;
10
+ }
11
+ }
4
12
  const blankRefState = () => ({
5
13
  options: [],
6
14
  selectedDisplay: [],
@@ -37,6 +45,14 @@ export class CoForm {
37
45
  this.disabled = false;
38
46
  /** Read-only mode for all fields. */
39
47
  this.readOnly = false;
48
+ /** Grid columns (1-4). Fields with `full: true` always span all columns. */
49
+ this.columns = 2;
50
+ /** Spacing between fields. */
51
+ this.gap = 'lg';
52
+ /** Footer alignment for the default submit button. */
53
+ this.footerAlign = 'end';
54
+ /** Hide the entire footer (use when embedded in a wizard or custom shell). */
55
+ this.hideFooter = false;
40
56
  this.snapshot = {
41
57
  values: {},
42
58
  errors: {},
@@ -46,6 +62,9 @@ export class CoForm {
46
62
  isSubmitting: false,
47
63
  };
48
64
  this.refStates = {};
65
+ /** Resolved theme — applied via <Host> in render() so data-attrs stick. */
66
+ this.themeStyles = null;
67
+ this.hasBg = false;
49
68
  this.controller = null;
50
69
  this.fields = [];
51
70
  this.unsubscribe = null;
@@ -112,15 +131,57 @@ export class CoForm {
112
131
  }
113
132
  componentWillLoad() {
114
133
  this.rebuildController();
134
+ this.applyTheme();
115
135
  }
116
136
  onSchemaChange() {
117
137
  this.rebuildController();
118
138
  }
139
+ onThemeChange() {
140
+ this.applyTheme();
141
+ }
119
142
  disconnectedCallback() {
120
143
  var _a;
121
144
  (_a = this.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(this);
122
145
  this.unsubscribe = null;
123
146
  }
147
+ /**
148
+ * Compute the resolved theme and store in `@State()`. The render method
149
+ * binds the styles + data-attrs via `<Host>` — no imperative setAttribute,
150
+ * which avoids the timing issues we hit with shadow:false.
151
+ */
152
+ applyTheme() {
153
+ if (!this.theme) {
154
+ this.themeStyles = null;
155
+ this.hasBg = false;
156
+ return;
157
+ }
158
+ const raw = typeof this.theme === 'string' ? safeParse(this.theme) : this.theme;
159
+ const resolved = resolveTheme(raw);
160
+ this.themeStyles = themeToStyles(resolved);
161
+ this.hasBg = !!(resolved.background.color || resolved.background.imageUrl);
162
+ }
163
+ /**
164
+ * Background-only styles applied to the Host. The host acts as the
165
+ * backdrop layer (full bleed) while the inner <form> is the centered
166
+ * card surface — same two-layer pattern as the wizard.
167
+ */
168
+ hostProps() {
169
+ const t = this.themeStyles;
170
+ if (!t)
171
+ return {};
172
+ return { style: t.inlineStyles };
173
+ }
174
+ /**
175
+ * Data attrs + CSS custom properties on the inner <form>. Stencil's
176
+ * shadow:false doesn't reliably rewrite `:host([attr])`, so the
177
+ * theme-driven selectors target `form[data-...]` instead.
178
+ */
179
+ formProps() {
180
+ const t = this.themeStyles;
181
+ if (!t)
182
+ return {};
183
+ return Object.assign(Object.assign(Object.assign({ 'data-theme': t.modifiers.colorScheme, 'data-density': t.modifiers.density, 'data-appearance': t.modifiers.appearance, 'data-radius': t.modifiers.radius }, (this.hasBg ? { 'data-has-bg': '' } : {})), (t.modifiers.hideHeader ? { 'data-hide-header': '' } : {})), { style: t.customProperties });
184
+ }
124
185
  // ── Controller wiring ─────────────────────────────────────────────────────
125
186
  rebuildController() {
126
187
  var _a;
@@ -197,6 +258,28 @@ export class CoForm {
197
258
  var _a;
198
259
  (_a = this.controller) === null || _a === void 0 ? void 0 : _a.touch(name);
199
260
  }
261
+ /**
262
+ * Validate and return the current values. Used by `<co-form-wizard>` to
263
+ * commit a page before advancing. If validation fails, marks all fields
264
+ * touched (so errors render) and returns `valid: false`.
265
+ */
266
+ async validateAndCollect() {
267
+ if (!this.controller)
268
+ return { valid: false, values: {} };
269
+ const valid = this.controller.validate();
270
+ if (!valid) {
271
+ this.controller.touchAll();
272
+ return { valid: false, values: this.controller.state.values };
273
+ }
274
+ const properties = this.controller.fields.reduce((acc, f) => {
275
+ acc[f.name] = f.ajvProperty;
276
+ return acc;
277
+ }, {});
278
+ return {
279
+ valid: true,
280
+ values: normalizeForValidation(this.controller.state.values, properties),
281
+ };
282
+ }
200
283
  hasSlotContent(slotName) {
201
284
  return !!this.host.querySelector(`[slot="${slotName}"]`);
202
285
  }
@@ -329,12 +412,23 @@ export class CoForm {
329
412
  }
330
413
  // ── Render ────────────────────────────────────────────────────────────────
331
414
  render() {
415
+ var _a;
332
416
  const excluded = this.excludedSet;
333
- const visible = this.fields.filter((f) => !f.hidden && !excluded.has(f.name));
334
- return (h("form", { key: '6cd71dc417b9a74b5131e9fe34955b4f4cced8a4', onSubmit: this.handleSubmit }, h("slot", { key: 'ef994d53f763fbe954f104a880bc675d60db8b79', name: "header" }), h("div", { key: '930d1f56c0f66e42441ffa57d71bcfdcd94d4677', class: `co-form-layout co-form-layout--${this.layout}` }, visible.map((f) => this.renderField(f))), h("div", { key: '2680a1eb2ddfc67773f247e226c1e8710514550f', class: "co-form-footer" }, h("slot", { key: '0c8adac9743830a4f990a5dfb3d0927ad5185d5f', name: "footer" }, h("co-button", { key: 'a092584e93fb6dd7623f537a4f9792f368454d45', type: "submit", label: "Enviar", variant: "primary" })))));
417
+ const visible = this.fields.filter((f) => {
418
+ if (excluded.has(f.name))
419
+ return false;
420
+ if (!this.controller)
421
+ return !f.hidden;
422
+ return this.controller.getFieldState(f.name).visible;
423
+ });
424
+ const layoutStyle = this.layout === 'grid'
425
+ ? { 'grid-template-columns': `repeat(${this.columns}, minmax(0, 1fr))` }
426
+ : undefined;
427
+ const customCss = (_a = this.themeStyles) === null || _a === void 0 ? void 0 : _a.customCss;
428
+ return (h(Host, Object.assign({}, this.hostProps()), h("form", Object.assign({ onSubmit: this.handleSubmit }, this.formProps()), customCss && h("style", null, customCss), h("slot", { name: "header" }), h("div", { class: `co-form-layout co-form-layout--${this.layout} co-form-layout--gap-${this.gap}`, style: layoutStyle }, visible.map((f) => (h("div", { key: f.name, class: "co-form-field-wrap", "data-full": f.fullWidth ? 'true' : 'false', "data-co-field": f.name, "data-co-field-kind": f.kind }, this.renderField(f))))), !this.hideFooter && (h("div", { class: `co-form-footer co-form-footer--${this.footerAlign}` }, h("slot", { name: "footer" }, h("co-button", { type: "submit", label: "Enviar", variant: "primary" })))))));
335
429
  }
336
430
  renderField(f) {
337
- var _a;
431
+ var _a, _b, _c, _d, _e, _f, _g, _h;
338
432
  // If the consumer provided a slot for this field, let it take over
339
433
  // regardless of the kind. Lets external HTML replace any field rendering.
340
434
  const slotName = `field:${f.name}`;
@@ -346,12 +440,14 @@ export class CoForm {
346
440
  const status = errors.length > 0 ? 'error' : 'default';
347
441
  const helperText = errors[0] || f.helperText;
348
442
  const value = this.snapshot.values[f.name];
349
- const disabled = this.disabled || f.disabled;
443
+ const runtime = (_a = this.controller) === null || _a === void 0 ? void 0 : _a.getFieldState(f.name);
444
+ const disabled = this.disabled || (runtime === null || runtime === void 0 ? void 0 : runtime.disabled) || f.disabled;
445
+ const required = (_b = runtime === null || runtime === void 0 ? void 0 : runtime.required) !== null && _b !== void 0 ? _b : f.required;
350
446
  const common = {
351
447
  key: f.name,
352
448
  label: f.label,
353
449
  placeholder: f.placeholder,
354
- required: f.required,
450
+ required,
355
451
  disabled,
356
452
  status: status,
357
453
  helperText: helperText,
@@ -371,8 +467,20 @@ export class CoForm {
371
467
  return (h("co-switch", Object.assign({}, common, { checked: !!value, onCoChange: (e) => this.handleFieldChange(f.name, e.detail) })));
372
468
  case 'select':
373
469
  return (h("co-select", Object.assign({}, common, { options: JSON.stringify(this.optionsFor(f)), value: String(value !== null && value !== void 0 ? value : ''), onCoChange: (e) => this.handleFieldChange(f.name, e.detail) })));
470
+ case 'rating':
471
+ return (h("co-rating", Object.assign({}, common, { value: typeof value === 'number' ? value : 0, max: Number((_c = f.originalAttrs.max) !== null && _c !== void 0 ? _c : 5), onCoChange: (e) => this.handleFieldChange(f.name, e.detail) })));
472
+ case 'slider':
473
+ return (h("co-slider", Object.assign({}, common, { value: typeof value === 'number' ? value : Number((_d = f.originalAttrs.min) !== null && _d !== void 0 ? _d : 0), min: Number((_e = f.originalAttrs.min) !== null && _e !== void 0 ? _e : 0), max: Number((_f = f.originalAttrs.max) !== null && _f !== void 0 ? _f : 100), step: Number((_g = f.originalAttrs.step) !== null && _g !== void 0 ? _g : 1), onCoChange: (e) => this.handleFieldChange(f.name, e.detail) })));
474
+ case 'color':
475
+ return (h("co-input", Object.assign({}, common, { type: "color", value: typeof value === 'string' ? value : '#000000', onCoInput: (e) => this.handleFieldChange(f.name, e.detail) })));
476
+ case 'section':
477
+ return (h("div", { class: "co-form-section", key: f.name }, h("h3", { class: "co-form-section__title" }, f.label), f.helperText && h("p", { class: "co-form-section__desc" }, f.helperText)));
478
+ case 'file':
479
+ return (h("co-input", Object.assign({}, common, { type: "file", onCoInput: (e) => this.handleFieldChange(f.name, e.detail) })));
480
+ case 'array':
481
+ return this.renderRepeater(f, value);
374
482
  case 'ref': {
375
- const refState = (_a = this.refStates[f.name]) !== null && _a !== void 0 ? _a : blankRefState();
483
+ const refState = (_h = this.refStates[f.name]) !== null && _h !== void 0 ? _h : blankRefState();
376
484
  const isMultiple = !!f.originalAttrs.multiple || f.originalAttrs.type === 'array';
377
485
  return (h("co-ref-field", Object.assign({}, common, { model: f.originalAttrs.ref, multiple: isMultiple, value: this.refValueAsString(f, value), options: JSON.stringify(refState.options), selectedDisplay: JSON.stringify(refState.selectedDisplay), loading: refState.loading, total: refState.total, onCoOpen: () => {
378
486
  // Lazy-load on first open if we haven't fetched yet.
@@ -392,6 +500,35 @@ export class CoForm {
392
500
  return [];
393
501
  return raw.map((v) => ({ label: String(v), value: String(v) }));
394
502
  }
503
+ // ── Repeater (kind: 'array') ──────────────────────────────────────────────
504
+ /**
505
+ * Renders a dynamic list of subforms. The schema for each row is taken from
506
+ * `itemSchema` (a Mongoose-style property map) on the field's originalAttrs.
507
+ * Values are stored as an array of objects.
508
+ */
509
+ renderRepeater(f, value) {
510
+ const rows = Array.isArray(value) ? value : [];
511
+ const itemSchema = f.originalAttrs.itemSchema || f.originalAttrs.objectShape || {};
512
+ const fieldNames = Object.keys(itemSchema);
513
+ const update = (next) => this.handleFieldChange(f.name, next);
514
+ const addRow = () => {
515
+ const blank = fieldNames.reduce((acc, n) => {
516
+ var _a;
517
+ acc[n] = (_a = itemSchema[n]) === null || _a === void 0 ? void 0 : _a.default;
518
+ return acc;
519
+ }, {});
520
+ update([...rows, blank]);
521
+ };
522
+ const removeRow = (idx) => update(rows.filter((_, i) => i !== idx));
523
+ const patchRow = (idx, name, v) => {
524
+ const next = rows.map((r, i) => (i === idx ? Object.assign(Object.assign({}, r), { [name]: v }) : r));
525
+ update(next);
526
+ };
527
+ return (h("div", { class: "co-form-repeater", key: f.name }, f.label && h("label", { class: "co-form-repeater__label" }, f.label), rows.map((row, idx) => (h("div", { class: "co-form-repeater__row", key: `${f.name}-${idx}` }, h("div", { class: "co-form-repeater__fields" }, fieldNames.map((n) => {
528
+ var _a, _b;
529
+ return (h("co-input", { key: n, label: ((_a = itemSchema[n]) === null || _a === void 0 ? void 0 : _a.label) || n, value: (_b = row[n]) !== null && _b !== void 0 ? _b : '', disabled: this.disabled || f.disabled, onCoInput: (e) => patchRow(idx, n, e.detail) }));
530
+ })), h("co-icon-button", { icon: "trash", variant: "ghost", onClick: () => removeRow(idx), disabled: this.disabled || f.disabled })))), h("co-button", { label: "Agregar", variant: "outlined", "icon-left": "plus", onClick: addRow, disabled: this.disabled || f.disabled })));
531
+ }
395
532
  static get is() { return "co-form"; }
396
533
  static get originalStyleUrls() {
397
534
  return {
@@ -596,13 +733,121 @@ export class CoForm {
596
733
  "reflect": false,
597
734
  "attribute": "read-only",
598
735
  "defaultValue": "false"
736
+ },
737
+ "columns": {
738
+ "type": "number",
739
+ "mutable": false,
740
+ "complexType": {
741
+ "original": "1 | 2 | 3 | 4",
742
+ "resolved": "1 | 2 | 3 | 4",
743
+ "references": {}
744
+ },
745
+ "required": false,
746
+ "optional": false,
747
+ "docs": {
748
+ "tags": [],
749
+ "text": "Grid columns (1-4). Fields with `full: true` always span all columns."
750
+ },
751
+ "getter": false,
752
+ "setter": false,
753
+ "reflect": false,
754
+ "attribute": "columns",
755
+ "defaultValue": "2"
756
+ },
757
+ "gap": {
758
+ "type": "string",
759
+ "mutable": false,
760
+ "complexType": {
761
+ "original": "'sm' | 'md' | 'lg'",
762
+ "resolved": "\"lg\" | \"md\" | \"sm\"",
763
+ "references": {}
764
+ },
765
+ "required": false,
766
+ "optional": false,
767
+ "docs": {
768
+ "tags": [],
769
+ "text": "Spacing between fields."
770
+ },
771
+ "getter": false,
772
+ "setter": false,
773
+ "reflect": false,
774
+ "attribute": "gap",
775
+ "defaultValue": "'lg'"
776
+ },
777
+ "footerAlign": {
778
+ "type": "string",
779
+ "mutable": false,
780
+ "complexType": {
781
+ "original": "'start' | 'end' | 'center' | 'between' | 'stretch'",
782
+ "resolved": "\"between\" | \"center\" | \"end\" | \"start\" | \"stretch\"",
783
+ "references": {}
784
+ },
785
+ "required": false,
786
+ "optional": false,
787
+ "docs": {
788
+ "tags": [],
789
+ "text": "Footer alignment for the default submit button."
790
+ },
791
+ "getter": false,
792
+ "setter": false,
793
+ "reflect": false,
794
+ "attribute": "footer-align",
795
+ "defaultValue": "'end'"
796
+ },
797
+ "hideFooter": {
798
+ "type": "boolean",
799
+ "mutable": false,
800
+ "complexType": {
801
+ "original": "boolean",
802
+ "resolved": "boolean",
803
+ "references": {}
804
+ },
805
+ "required": false,
806
+ "optional": false,
807
+ "docs": {
808
+ "tags": [],
809
+ "text": "Hide the entire footer (use when embedded in a wizard or custom shell)."
810
+ },
811
+ "getter": false,
812
+ "setter": false,
813
+ "reflect": false,
814
+ "attribute": "hide-footer",
815
+ "defaultValue": "false"
816
+ },
817
+ "theme": {
818
+ "type": "string",
819
+ "mutable": false,
820
+ "complexType": {
821
+ "original": "string | ThemeSpec",
822
+ "resolved": "ThemeSpec | string | undefined",
823
+ "references": {
824
+ "ThemeSpec": {
825
+ "location": "import",
826
+ "path": "@prolibu-suite/cobalt-form-core",
827
+ "id": "../form-core/dist/index.d.ts::ThemeSpec",
828
+ "referenceLocation": "ThemeSpec"
829
+ }
830
+ }
831
+ },
832
+ "required": false,
833
+ "optional": true,
834
+ "docs": {
835
+ "tags": [],
836
+ "text": "Theme overrides. JSON string OR a ThemeSpec assigned as a JS property.\nApplied as scoped CSS custom properties on the host so global tokens are\nnever touched. See `@prolibu-suite/cobalt-form-core` `resolveTheme` for\naccepted shape and defaults."
837
+ },
838
+ "getter": false,
839
+ "setter": false,
840
+ "reflect": false,
841
+ "attribute": "theme"
599
842
  }
600
843
  };
601
844
  }
602
845
  static get states() {
603
846
  return {
604
847
  "snapshot": {},
605
- "refStates": {}
848
+ "refStates": {},
849
+ "themeStyles": {},
850
+ "hasBg": {}
606
851
  };
607
852
  }
608
853
  static get events() {
@@ -731,6 +976,27 @@ export class CoForm {
731
976
  "text": "Mark a field as touched (forces its errors to be visible).",
732
977
  "tags": []
733
978
  }
979
+ },
980
+ "validateAndCollect": {
981
+ "complexType": {
982
+ "signature": "() => Promise<{ valid: boolean; values: Record<string, any>; }>",
983
+ "parameters": [],
984
+ "references": {
985
+ "Promise": {
986
+ "location": "global",
987
+ "id": "global::Promise"
988
+ },
989
+ "Record": {
990
+ "location": "global",
991
+ "id": "global::Record"
992
+ }
993
+ },
994
+ "return": "Promise<{ valid: boolean; values: Record<string, any>; }>"
995
+ },
996
+ "docs": {
997
+ "text": "Validate and return the current values. Used by `<co-form-wizard>` to\ncommit a page before advancing. If validation fails, marks all fields\ntouched (so errors render) and returns `valid: false`.",
998
+ "tags": []
999
+ }
734
1000
  }
735
1001
  };
736
1002
  }
@@ -745,6 +1011,9 @@ export class CoForm {
745
1011
  }, {
746
1012
  "propName": "locale",
747
1013
  "methodName": "onSchemaChange"
1014
+ }, {
1015
+ "propName": "theme",
1016
+ "methodName": "onThemeChange"
748
1017
  }];
749
1018
  }
750
1019
  }