@vectoriox/iox-ui 5.27.0 → 5.29.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/design-core/index.d.ts +186 -7
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +201 -11
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs +119 -5
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +591 -181
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +189 -16
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/forms-core/index.d.ts +59 -3
- package/generation-core/index.d.ts +206 -74
- package/index.d.ts +30 -3
- package/package.json +1 -1
- package/src/design-core/contrast-audit.ts +7 -6
- package/src/design-core/flavor-spec.generated.ts +170 -5
- package/src/design-core/node-links.ts +32 -0
- package/src/design-core/public-api.ts +1 -0
- package/src/forms-core/collect.ts +3 -1
- package/src/forms-core/form-presets.ts +105 -0
- package/src/forms-core/options.ts +29 -0
- package/src/forms-core/public-api.ts +2 -0
- package/src/forms-core/transport.ts +6 -2
- package/src/generation-core/baseline-roles.ts +75 -0
- package/src/generation-core/compose-site.ts +256 -95
- package/src/generation-core/node-baselines.generated.ts +90 -0
- package/src/generation-core/patterns.ts +31 -9
- package/src/generation-core/public-api.ts +1 -0
- package/src/generation-core/serialized-node.ts +13 -2
- package/src/generation-core/site-plan.model.ts +4 -3
- package/src/generation-core/site-spec.generated.ts +110 -57
- package/src/generation-core/validate-site-plan.ts +52 -20
- package/styles/iox-forms.css +136 -8
|
@@ -1245,6 +1245,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
1245
1245
|
args: ['childrenHost', { read: ViewContainerRef, static: true }]
|
|
1246
1246
|
}] } });
|
|
1247
1247
|
|
|
1248
|
+
/**
|
|
1249
|
+
* Where a node links (W1.1). Link and LinkedContainer always had two link types — an external URL or a
|
|
1250
|
+
* website PAGE by its route — and ButtonBlock now has the same pair, so a composed "Get in touch" button
|
|
1251
|
+
* is a page link the builder shows as `/contact`, not a hand-built URL. Framework-free: the composer
|
|
1252
|
+
* (generation-core) writes it, the builder canvas and both engine render paths read it.
|
|
1253
|
+
*/
|
|
1254
|
+
var LinkType;
|
|
1255
|
+
(function (LinkType) {
|
|
1256
|
+
LinkType["External"] = "external";
|
|
1257
|
+
LinkType["Page"] = "page";
|
|
1258
|
+
})(LinkType || (LinkType = {}));
|
|
1259
|
+
const ANCHOR_TAG = 'a';
|
|
1260
|
+
/**
|
|
1261
|
+
* The href a ButtonBlock renders, or `null` for none (it is a `<button>`, or a link with no target).
|
|
1262
|
+
* Only an `<a>` links. A page link's route goes through `pageHref` — the engine's `resolvePageHref`
|
|
1263
|
+
* (`:params` substituted, base-relative); the builder canvas passes none and shows the route as authored,
|
|
1264
|
+
* as its Link does.
|
|
1265
|
+
*/
|
|
1266
|
+
function buttonBlockHref(props, pageHref = route => route) {
|
|
1267
|
+
if (props.tag !== ANCHOR_TAG)
|
|
1268
|
+
return null;
|
|
1269
|
+
const href = props.linkType === LinkType.Page ? pageHref(String(props.pageRoute ?? '')) : String(props.href ?? '');
|
|
1270
|
+
return href || null;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1248
1273
|
/**
|
|
1249
1274
|
* Directive-host ButtonBlock: the real `<a>`/`<button>` IS the host — no `app-button-block`
|
|
1250
1275
|
* wrapper, no inner/outer split. The tag is chosen by whoever CREATES the component (engine
|
|
@@ -1252,30 +1277,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
1252
1277
|
* The `iox-btn-block` class (UA resets: inline-flex, color/font inherit, no border) matches the
|
|
1253
1278
|
* string renderer and lives in the engine's global styles.scss — kept OFF `:host` so styleProps
|
|
1254
1279
|
* (`.iox-node-{id}`) can override it. Children render into the host via `childrenHost`.
|
|
1280
|
+
* A page link (`linkType: page`) arrives with its `pageRoute` already resolved by the engine (params
|
|
1281
|
+
* substituted, base-relative — the Link's resolver); `buttonBlockHref` decides the href for every renderer.
|
|
1255
1282
|
*/
|
|
1256
1283
|
class ButtonBlockComponent {
|
|
1257
1284
|
constructor() {
|
|
1258
1285
|
this.tag = 'button';
|
|
1286
|
+
this.linkType = LinkType.External;
|
|
1259
1287
|
this.href = '';
|
|
1288
|
+
this.pageRoute = '';
|
|
1260
1289
|
this.target = '_self';
|
|
1261
1290
|
this.type = 'button';
|
|
1262
1291
|
this.nodeId = '';
|
|
1263
1292
|
}
|
|
1293
|
+
get resolvedHref() {
|
|
1294
|
+
return buttonBlockHref(this);
|
|
1295
|
+
}
|
|
1264
1296
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ButtonBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1265
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: ButtonBlockComponent, isStandalone: false, selector: "a[iox-button-block],button[iox-button-block]", inputs: { tag: "tag", href: "href", target: "target", type: "type", nodeId: "nodeId" }, host: { properties: { "class": "'iox-btn-block iox-node-' + nodeId", "attr.href": "
|
|
1297
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: ButtonBlockComponent, isStandalone: false, selector: "a[iox-button-block],button[iox-button-block]", inputs: { tag: "tag", linkType: "linkType", href: "href", pageRoute: "pageRoute", target: "target", type: "type", nodeId: "nodeId" }, host: { properties: { "class": "'iox-btn-block iox-node-' + nodeId", "attr.href": "resolvedHref", "attr.target": "tag === 'a' ? (target || null) : null", "attr.type": "tag === 'a' ? null : type" } }, viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `<ng-container #childrenHost></ng-container>`, isInline: true, styles: [":host{box-sizing:border-box}\n"] }); }
|
|
1266
1298
|
}
|
|
1267
1299
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ButtonBlockComponent, decorators: [{
|
|
1268
1300
|
type: Component,
|
|
1269
1301
|
args: [{ selector: 'a[iox-button-block],button[iox-button-block]', template: `<ng-container #childrenHost></ng-container>`, host: {
|
|
1270
1302
|
'[class]': "'iox-btn-block iox-node-' + nodeId",
|
|
1271
|
-
'[attr.href]':
|
|
1303
|
+
'[attr.href]': 'resolvedHref',
|
|
1272
1304
|
'[attr.target]': "tag === 'a' ? (target || null) : null",
|
|
1273
1305
|
'[attr.type]': "tag === 'a' ? null : type",
|
|
1274
1306
|
}, standalone: false, styles: [":host{box-sizing:border-box}\n"] }]
|
|
1275
1307
|
}], propDecorators: { tag: [{
|
|
1276
1308
|
type: Input
|
|
1309
|
+
}], linkType: [{
|
|
1310
|
+
type: Input
|
|
1277
1311
|
}], href: [{
|
|
1278
1312
|
type: Input
|
|
1313
|
+
}], pageRoute: [{
|
|
1314
|
+
type: Input
|
|
1279
1315
|
}], target: [{
|
|
1280
1316
|
type: Input
|
|
1281
1317
|
}], type: [{
|
|
@@ -3289,6 +3325,36 @@ const files = (value) => Array.isArray(value) ? value
|
|
|
3289
3325
|
: value && typeof value === 'object' ? [value]
|
|
3290
3326
|
: [];
|
|
3291
3327
|
|
|
3328
|
+
/** `value|Label` separates a submitted value from what the visitor reads; a bare line is both. */
|
|
3329
|
+
const OPTION_SEPARATOR = '|';
|
|
3330
|
+
/**
|
|
3331
|
+
* A Field's `optionsText` trait — the author's choices, one per line — as options. ONE parser for the
|
|
3332
|
+
* builder canvas, both engine paths and iox-cms: until 2026-10-19 nothing parsed the trait, so a Radio
|
|
3333
|
+
* group or a Dropdown from the tray rendered no choices at all on the canvas and on the live site.
|
|
3334
|
+
*/
|
|
3335
|
+
function parseFieldOptions(text) {
|
|
3336
|
+
if (typeof text !== 'string')
|
|
3337
|
+
return [];
|
|
3338
|
+
const out = [];
|
|
3339
|
+
for (const line of text.split(/\r?\n/)) {
|
|
3340
|
+
const raw = line.trim();
|
|
3341
|
+
if (!raw)
|
|
3342
|
+
continue;
|
|
3343
|
+
const at = raw.indexOf(OPTION_SEPARATOR);
|
|
3344
|
+
const value = (at === -1 ? raw : raw.slice(0, at)).trim();
|
|
3345
|
+
const label = (at === -1 ? raw : raw.slice(at + 1)).trim() || value;
|
|
3346
|
+
if (value)
|
|
3347
|
+
out.push({ value, label });
|
|
3348
|
+
}
|
|
3349
|
+
return out;
|
|
3350
|
+
}
|
|
3351
|
+
/** The choices a saved Field offers: an explicit `options` list wins, else its `optionsText` parsed. */
|
|
3352
|
+
function fieldOptionsOf(props) {
|
|
3353
|
+
if (Array.isArray(props?.options) && props.options.length)
|
|
3354
|
+
return props.options;
|
|
3355
|
+
return parseFieldOptions(props?.optionsText);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3292
3358
|
// Derive a FormSpec from a node tree.
|
|
3293
3359
|
//
|
|
3294
3360
|
// The tree the author edited is the SINGLE source of truth for "what fields does this form have".
|
|
@@ -3392,8 +3458,9 @@ function toFieldSpec(node, taken) {
|
|
|
3392
3458
|
const rules = normaliseRules(props['rules']);
|
|
3393
3459
|
if (rules.length)
|
|
3394
3460
|
spec.rules = rules;
|
|
3395
|
-
|
|
3396
|
-
|
|
3461
|
+
const options = fieldOptionsOf(props);
|
|
3462
|
+
if (options.length)
|
|
3463
|
+
spec.options = options;
|
|
3397
3464
|
if (props['optionsSource'])
|
|
3398
3465
|
spec.optionsSource = props['optionsSource'];
|
|
3399
3466
|
if (props['defaultValue'] !== undefined && props['defaultValue'] !== '') {
|
|
@@ -3609,10 +3676,14 @@ const RENDERED_AT_FIELD = '_iox_t';
|
|
|
3609
3676
|
* The public submit path. Built here rather than interpolated at each call site, so the engine and
|
|
3610
3677
|
* any future host cannot disagree about it — and so changing it is one edit.
|
|
3611
3678
|
*
|
|
3612
|
-
*
|
|
3679
|
+
* ⚠️ ROOT-relative (leading slash), never base-relative. A live site is served under a per-org
|
|
3680
|
+
* `<base href="/<label>/">`, so a relative `api/…` resolved to `/<label>/api/…` — a path the reverse
|
|
3681
|
+
* proxy (`/api` → iox-cms, everything else → the engine) hands to the ENGINE, which answers a POST with
|
|
3682
|
+
* 404: no visitor submission ever reached iox-cms from an org path (found 2026-10-19 by
|
|
3683
|
+
* e2e:forminsert:real). The org is already in the path segment after `site/`.
|
|
3613
3684
|
*/
|
|
3614
3685
|
function submitEndpointPath(orgId, formId) {
|
|
3615
|
-
return
|
|
3686
|
+
return `/api/site/${encodeURIComponent(orgId)}/forms/${encodeURIComponent(formId)}/submit`;
|
|
3616
3687
|
}
|
|
3617
3688
|
/**
|
|
3618
3689
|
* Add the anti-spam fields to the collected values.
|
|
@@ -3657,6 +3728,85 @@ function normaliseErrors(raw) {
|
|
|
3657
3728
|
return Object.keys(errors).length ? errors : undefined;
|
|
3658
3729
|
}
|
|
3659
3730
|
|
|
3731
|
+
/**
|
|
3732
|
+
* FORM PRESETS — a complete, working form as one insert: its fields, its submit label and the words of
|
|
3733
|
+
* its Success and Error blocks. ONE definition (W1.1): the builder's Forms tray builds a preset from
|
|
3734
|
+
* here (`buildFormPreset`, iox-builder), and the site composer (`generation-core`) stamps the SAME preset
|
|
3735
|
+
* into a composed page, so a generated contact form is the real one — never a second, drifting copy.
|
|
3736
|
+
* Data only: framework-free, like the rest of forms-core.
|
|
3737
|
+
*/
|
|
3738
|
+
var FormPresetId;
|
|
3739
|
+
(function (FormPresetId) {
|
|
3740
|
+
FormPresetId["Contact"] = "contact";
|
|
3741
|
+
FormPresetId["Newsletter"] = "newsletter";
|
|
3742
|
+
FormPresetId["Application"] = "application";
|
|
3743
|
+
FormPresetId["Booking"] = "booking";
|
|
3744
|
+
})(FormPresetId || (FormPresetId = {}));
|
|
3745
|
+
const FORM_PRESETS = Object.freeze([
|
|
3746
|
+
{
|
|
3747
|
+
id: FormPresetId.Contact, name: 'Contact', icon: 'ph-thin ph-envelope-simple',
|
|
3748
|
+
description: 'Name, email and a message. Stored, and emailed to you.',
|
|
3749
|
+
formName: 'Contact form',
|
|
3750
|
+
fields: [
|
|
3751
|
+
{ label: 'Full name', control: 'text', required: true },
|
|
3752
|
+
{ label: 'Email', control: 'email', required: true },
|
|
3753
|
+
{ label: 'Message', control: 'textarea', required: true, rows: 5 },
|
|
3754
|
+
],
|
|
3755
|
+
submitLabel: 'Send message',
|
|
3756
|
+
successText: 'Thanks — we’ll be in touch shortly.',
|
|
3757
|
+
errorText: 'We couldn’t send your message. Please try again — your answers are still here.',
|
|
3758
|
+
},
|
|
3759
|
+
{
|
|
3760
|
+
id: FormPresetId.Newsletter, name: 'Newsletter', icon: 'ph-thin ph-paper-plane-tilt',
|
|
3761
|
+
description: 'One email field plus consent. Stored.',
|
|
3762
|
+
formName: 'Newsletter signup',
|
|
3763
|
+
fields: [
|
|
3764
|
+
{ label: 'Email', control: 'email', required: true, placeholder: 'you@company.com' },
|
|
3765
|
+
// Consent is a checkbox that must be TICKED — `required` on a checkbox becomes the
|
|
3766
|
+
// `accepted` rule, with its own message, rather than a generic "this is required".
|
|
3767
|
+
{ label: 'Email me occasional updates', control: 'checkbox', required: true },
|
|
3768
|
+
],
|
|
3769
|
+
submitLabel: 'Subscribe',
|
|
3770
|
+
successText: 'You’re on the list.',
|
|
3771
|
+
errorText: 'We couldn’t sign you up. Please try again.',
|
|
3772
|
+
},
|
|
3773
|
+
{
|
|
3774
|
+
id: FormPresetId.Application, name: 'Application', icon: 'ph-thin ph-read-cv-logo',
|
|
3775
|
+
description: 'Details plus a CV upload. Stored, with an auto-reply.',
|
|
3776
|
+
formName: 'Application',
|
|
3777
|
+
fields: [
|
|
3778
|
+
{ label: 'Full name', control: 'text', required: true },
|
|
3779
|
+
{ label: 'Email', control: 'email', required: true },
|
|
3780
|
+
{ label: 'Phone', control: 'tel' },
|
|
3781
|
+
{ label: 'Role', control: 'select', required: true, optionsText: 'Design\nEngineering\nOther' },
|
|
3782
|
+
{ label: 'CV', control: 'file', required: true, help: 'PDF or Word, up to 5MB' },
|
|
3783
|
+
{ label: 'Anything else', control: 'textarea', rows: 4 },
|
|
3784
|
+
],
|
|
3785
|
+
submitLabel: 'Apply',
|
|
3786
|
+
successText: 'Application received — we’ll read it properly and reply.',
|
|
3787
|
+
errorText: 'We couldn’t submit your application. Please try again.',
|
|
3788
|
+
},
|
|
3789
|
+
{
|
|
3790
|
+
id: FormPresetId.Booking, name: 'Booking request', icon: 'ph-thin ph-calendar-check',
|
|
3791
|
+
description: 'Date and party size. Stored, and it notifies you.',
|
|
3792
|
+
formName: 'Booking request',
|
|
3793
|
+
fields: [
|
|
3794
|
+
{ label: 'Name', control: 'text', required: true },
|
|
3795
|
+
{ label: 'Email', control: 'email', required: true },
|
|
3796
|
+
{ label: 'Date', control: 'date', required: true },
|
|
3797
|
+
{ label: 'People', control: 'number', required: true },
|
|
3798
|
+
{ label: 'Notes', control: 'textarea', rows: 3, help: 'Allergies, access needs, anything useful' },
|
|
3799
|
+
],
|
|
3800
|
+
submitLabel: 'Request a booking',
|
|
3801
|
+
successText: 'Request received — we’ll confirm by email.',
|
|
3802
|
+
errorText: 'We couldn’t send your request. Please try again.',
|
|
3803
|
+
},
|
|
3804
|
+
]);
|
|
3805
|
+
/** The preset with this id, or undefined. */
|
|
3806
|
+
function formPresetById(id) {
|
|
3807
|
+
return FORM_PRESETS.find(p => p.id === id);
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3660
3810
|
/*
|
|
3661
3811
|
* @vectoriox/iox-ui/forms-core — the shared, FRAMEWORK-FREE core of the forms feature.
|
|
3662
3812
|
*
|
|
@@ -4068,6 +4218,19 @@ let fieldSeq = 0;
|
|
|
4068
4218
|
* and choosing `email` also chooses email validation.
|
|
4069
4219
|
*/
|
|
4070
4220
|
class IoxFieldComponent {
|
|
4221
|
+
/** Explicit choices (bound data, a hand-built node). Wins over `optionsText`. */
|
|
4222
|
+
set options(value) {
|
|
4223
|
+
this._options = Array.isArray(value) ? value : [];
|
|
4224
|
+
this.refreshChoices();
|
|
4225
|
+
}
|
|
4226
|
+
get options() { return this._options; }
|
|
4227
|
+
/** The author's choices, one per line (`value|Label`) — the Field's trait. A SETTER: the builder
|
|
4228
|
+
* assigns traits directly, with no ngOnChanges. */
|
|
4229
|
+
set optionsText(value) {
|
|
4230
|
+
this._optionsText = value ?? '';
|
|
4231
|
+
this.refreshChoices();
|
|
4232
|
+
}
|
|
4233
|
+
get optionsText() { return this._optionsText; }
|
|
4071
4234
|
/**
|
|
4072
4235
|
* Classic constructor injection, not `inject()`: this library's specs construct components
|
|
4073
4236
|
* directly (its vitest setup cannot run `TestBed.createComponent`), and a field initializer
|
|
@@ -4095,7 +4258,10 @@ class IoxFieldComponent {
|
|
|
4095
4258
|
this.rangeMax = 100;
|
|
4096
4259
|
this.rangeStep = 1;
|
|
4097
4260
|
this.accept = '';
|
|
4098
|
-
|
|
4261
|
+
/** What the radio group / dropdown renders: the shared forms-core reading of the two inputs above. */
|
|
4262
|
+
this.choices = [];
|
|
4263
|
+
this._options = [];
|
|
4264
|
+
this._optionsText = '';
|
|
4099
4265
|
this.value = '';
|
|
4100
4266
|
/** Rules beyond `required` — carried on the node, interpreted by forms-core. */
|
|
4101
4267
|
this.rules = [];
|
|
@@ -4150,10 +4316,13 @@ class IoxFieldComponent {
|
|
|
4150
4316
|
spec.required = true;
|
|
4151
4317
|
if (this.rules?.length)
|
|
4152
4318
|
spec.rules = this.rules;
|
|
4153
|
-
if (this.
|
|
4154
|
-
spec.options = this.
|
|
4319
|
+
if (this.choices.length)
|
|
4320
|
+
spec.options = this.choices;
|
|
4155
4321
|
return spec;
|
|
4156
4322
|
}
|
|
4323
|
+
refreshChoices() {
|
|
4324
|
+
this.choices = fieldOptionsOf({ options: this._options, optionsText: this._optionsText });
|
|
4325
|
+
}
|
|
4157
4326
|
/** `file`, `date`, `number`… map straight through; everything else is a text input. */
|
|
4158
4327
|
get inputType() {
|
|
4159
4328
|
const passthrough = ['email', 'tel', 'url', 'number', 'password', 'date', 'time', 'file', 'hidden'];
|
|
@@ -4179,7 +4348,7 @@ class IoxFieldComponent {
|
|
|
4179
4348
|
this.touched.emit();
|
|
4180
4349
|
}
|
|
4181
4350
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxFieldComponent, deps: [{ token: FormRuntimeService, optional: true }, { token: IOX_FORM_MESSAGES }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4182
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: IoxFieldComponent, isStandalone: false, selector: "div[iox-field]", inputs: { nodeId: "nodeId", name: "name", label: "label", control: "control", placeholder: "placeholder", help: "help", required: "required", disabled: "disabled", labelHidden: "labelHidden", requiredMark: "requiredMark", rows: "rows", rangeMin: "rangeMin", rangeMax: "rangeMax", rangeStep: "rangeStep", accept: "accept", options: "options", value: "value", rules: "rules", forceInvalid: "forceInvalid", designErrorText: "designErrorText", showValid: "showValid" }, outputs: { valueChange: "valueChange", touched: "touched" }, host: { properties: { "class": "'iox-field iox-node-' + nodeId", "class.iox-invalid": "invalid", "class.iox-valid": "showValid", "attr.data-iox-field": "fieldName || null", "hidden": "control === 'hidden' ? true : null" } }, ngImport: i0, template: `
|
|
4351
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: IoxFieldComponent, isStandalone: false, selector: "div[iox-field]", inputs: { nodeId: "nodeId", name: "name", label: "label", control: "control", placeholder: "placeholder", help: "help", required: "required", disabled: "disabled", labelHidden: "labelHidden", requiredMark: "requiredMark", rows: "rows", rangeMin: "rangeMin", rangeMax: "rangeMax", rangeStep: "rangeStep", accept: "accept", options: "options", optionsText: "optionsText", value: "value", rules: "rules", forceInvalid: "forceInvalid", designErrorText: "designErrorText", showValid: "showValid" }, outputs: { valueChange: "valueChange", touched: "touched" }, host: { properties: { "class": "'iox-field iox-node-' + nodeId", "class.iox-invalid": "invalid", "class.iox-valid": "showValid", "attr.data-iox-field": "fieldName || null", "hidden": "control === 'hidden' ? true : null" } }, ngImport: i0, template: `
|
|
4183
4352
|
<label class="iox-field__label"
|
|
4184
4353
|
[class.iox-field__label--hidden]="labelHidden"
|
|
4185
4354
|
[attr.for]="controlId">
|
|
@@ -4215,7 +4384,7 @@ class IoxFieldComponent {
|
|
|
4215
4384
|
(change)="emit($any($event.target).value)"
|
|
4216
4385
|
(blur)="touch()">
|
|
4217
4386
|
<option value="" [selected]="!value">{{ placeholder || '' }}</option>
|
|
4218
|
-
@for (option of
|
|
4387
|
+
@for (option of choices; track option.value) {
|
|
4219
4388
|
<option [value]="option.value" [selected]="option.value === value">{{ option.label }}</option>
|
|
4220
4389
|
}
|
|
4221
4390
|
</select>
|
|
@@ -4262,9 +4431,10 @@ class IoxFieldComponent {
|
|
|
4262
4431
|
}
|
|
4263
4432
|
@case ('radio') {
|
|
4264
4433
|
<div class="iox-field__group" role="radiogroup" [attr.aria-labelledby]="controlId + '-label'">
|
|
4265
|
-
@for (option of
|
|
4434
|
+
@for (option of choices; track option.value) {
|
|
4266
4435
|
<label class="iox-field__option">
|
|
4267
|
-
<input
|
|
4436
|
+
<input class="iox-field__control iox-field__control--radio"
|
|
4437
|
+
type="radio"
|
|
4268
4438
|
[name]="fieldName"
|
|
4269
4439
|
[value]="option.value"
|
|
4270
4440
|
[checked]="option.value === value"
|
|
@@ -4339,7 +4509,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4339
4509
|
(change)="emit($any($event.target).value)"
|
|
4340
4510
|
(blur)="touch()">
|
|
4341
4511
|
<option value="" [selected]="!value">{{ placeholder || '' }}</option>
|
|
4342
|
-
@for (option of
|
|
4512
|
+
@for (option of choices; track option.value) {
|
|
4343
4513
|
<option [value]="option.value" [selected]="option.value === value">{{ option.label }}</option>
|
|
4344
4514
|
}
|
|
4345
4515
|
</select>
|
|
@@ -4386,9 +4556,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4386
4556
|
}
|
|
4387
4557
|
@case ('radio') {
|
|
4388
4558
|
<div class="iox-field__group" role="radiogroup" [attr.aria-labelledby]="controlId + '-label'">
|
|
4389
|
-
@for (option of
|
|
4559
|
+
@for (option of choices; track option.value) {
|
|
4390
4560
|
<label class="iox-field__option">
|
|
4391
|
-
<input
|
|
4561
|
+
<input class="iox-field__control iox-field__control--radio"
|
|
4562
|
+
type="radio"
|
|
4392
4563
|
[name]="fieldName"
|
|
4393
4564
|
[value]="option.value"
|
|
4394
4565
|
[checked]="option.value === value"
|
|
@@ -4467,6 +4638,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4467
4638
|
type: Input
|
|
4468
4639
|
}], options: [{
|
|
4469
4640
|
type: Input
|
|
4641
|
+
}], optionsText: [{
|
|
4642
|
+
type: Input
|
|
4470
4643
|
}], value: [{
|
|
4471
4644
|
type: Input
|
|
4472
4645
|
}], rules: [{
|