@vectoriox/iox-ui 5.82.2 → 5.83.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 (32) hide show
  1. package/design-core/index.d.ts +200 -0
  2. package/fesm2022/vectoriox-iox-ui-design-core.mjs +215 -1
  3. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  4. package/fesm2022/vectoriox-iox-ui-forms-core.mjs +733 -56
  5. package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
  6. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +40 -4
  7. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
  8. package/fesm2022/vectoriox-iox-ui.mjs +1547 -135
  9. package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
  10. package/forms-core/index.d.ts +366 -18
  11. package/generation-core/index.d.ts +3 -0
  12. package/index.d.ts +303 -25
  13. package/package.json +1 -1
  14. package/src/design-core/contrast-audit.ts +4 -0
  15. package/src/design-core/flavor-spec.generated.ts +209 -1
  16. package/src/forms-core/channel.ts +85 -0
  17. package/src/forms-core/collect.ts +63 -30
  18. package/src/forms-core/dates.ts +299 -0
  19. package/src/forms-core/field-markup.ts +159 -0
  20. package/src/forms-core/form-presets.ts +51 -2
  21. package/src/forms-core/form.model.ts +34 -2
  22. package/src/forms-core/messages.ts +31 -0
  23. package/src/forms-core/public-api.ts +3 -0
  24. package/src/forms-core/rule-catalog.ts +3 -2
  25. package/src/forms-core/submit-lifecycle.ts +42 -12
  26. package/src/forms-core/validate.ts +90 -4
  27. package/src/generation-core/audit-site.ts +9 -1
  28. package/src/generation-core/compose-site.ts +22 -3
  29. package/src/generation-core/node-baselines.generated.ts +10 -0
  30. package/src/generation-core/plan-wire.ts +2 -2
  31. package/styles/iox-design.css +10 -0
  32. package/styles/iox-forms.css +349 -10
@@ -1,5 +1,5 @@
1
1
  import { NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, elementStyleById, ownedTraitsOf, applyElementStyle, flavorPackById, parseHexColor, SPEC_AXES, EntranceParam, staggerDelay, LinkType, COLOR_SCHEMES, setColorScheme, resolveDesignChoices, ANIMATE_IDS, auditDesignContrast } from '@vectoriox/iox-ui/design-core';
2
- import { formPresetById, FORM_PRESETS, ControlType, FORM_NODE_TYPE, collectFormSpec, FIELD_NODE_TYPE } from '@vectoriox/iox-ui/forms-core';
2
+ import { formPresetById, FORM_ACTIONS_ROLE, channelOpensNewTab, FORM_CHANNEL_ROLE, planFormPresetIds, ControlType, FORM_NODE_TYPE, collectFormSpec, FIELD_NODE_TYPE, isChannelFilled } from '@vectoriox/iox-ui/forms-core';
3
3
  import { normalizeFrameSequence, filmScreensFor, FILM_PREPARED_PICTURES, FrameExtension } from '@vectoriox/iox-ui/media-core';
4
4
  import { SEO_DESCRIPTION_MAX, SEO_TITLE_MAX } from '@vectoriox/iox-ui/seo-core';
5
5
  import { pickTabsAutoBinds, pickAccordionAutoBinds, segmentedOptions, ITEM_OPEN_STATE, CardPart, CardRole, CardLayout, CARD_LAYOUTS, CARD_PARTS, BADGE_TONES, buildCardRecipe, findByRole, buildSegmentedRecipe, AccordionRole, buildAccordionRecipe, ACCORDION_WORDS, accordionItems, accordionQuestionOf, accordionAnswerOf, ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, buildTabsRecipe, TABS_WORDS, tabsTriggers, tabLabelNodeOf, TABS_LABEL_BIND_PROP, tabsPanels, tabPanelTextOf, TABS_TEXT_BIND_PROP, tabPanelImageOf, tabsListOf, tabsPanelsOf, TABS_CONTENT_LIMIT, TABS_IMAGE_BIND_PROP, buildCarouselRecipe, SLIDE_TYPE, buildLightboxRecipe, LIGHTBOX_LABEL, SLIDE_TO_ROW } from '@vectoriox/iox-ui/composite-core';
@@ -9610,6 +9610,10 @@ const NODE_BASELINES = {
9610
9610
  "label": "Label",
9611
9611
  "control": "text",
9612
9612
  "placeholder": "",
9613
+ "multiple": false,
9614
+ "appearance": "chips",
9615
+ "earliest": "",
9616
+ "latest": "",
9613
9617
  "help": "",
9614
9618
  "rows": 4,
9615
9619
  "rangeMin": 0,
@@ -9713,6 +9717,10 @@ const NODE_BASELINES = {
9713
9717
  "label",
9714
9718
  "control",
9715
9719
  "placeholder",
9720
+ "multiple",
9721
+ "appearance",
9722
+ "earliest",
9723
+ "latest",
9716
9724
  "help",
9717
9725
  "rows",
9718
9726
  "rangeMin",
@@ -10016,6 +10024,7 @@ const NODE_BASELINES = {
10016
10024
  "props": {
10017
10025
  "label": "Send",
10018
10026
  "loadingLabel": "Sending…",
10027
+ "sentLabel": "",
10019
10028
  "disableWhileSubmitting": true
10020
10029
  },
10021
10030
  "styleProps": {
@@ -10107,6 +10116,7 @@ const NODE_BASELINES = {
10107
10116
  "traits": [
10108
10117
  "label",
10109
10118
  "loadingLabel",
10119
+ "sentLabel",
10110
10120
  "disableWhileSubmitting"
10111
10121
  ],
10112
10122
  "styleTraits": [
@@ -13186,7 +13196,10 @@ const FORM_SUCCESS = 'FormSuccess';
13186
13196
  const FORM_ERROR = 'FormError';
13187
13197
  const FORM_STATE_BLOCKS = [FORM_SUCCESS, FORM_ERROR];
13188
13198
  /** The Field traits a preset field fills, 1:1 (`FormPresetField`). */
13189
- const PRESET_FIELD_PROPS = ['label', 'control', 'required', 'help', 'placeholder', 'optionsText', 'rows'];
13199
+ const PRESET_FIELD_PROPS = ['label', 'control', 'required', 'help', 'placeholder', 'optionsText', 'rows', 'multiple', 'appearance', 'earliest', 'latest'];
13200
+ const FIELD_GROUP = 'FieldGroup';
13201
+ /** The second channel beside Send (premium N2): the quieter button. */
13202
+ const CHANNEL_STYLE = 'button-secondary';
13190
13203
  const LABEL_TAG = 'span';
13191
13204
  const TEXT = 'Text';
13192
13205
  /** A button's label reads the button's own type and ink (contract element style, W1.1 Q6). */
@@ -13414,7 +13427,22 @@ function presetChildren(preset, id) {
13414
13427
  block.children = [create(`${id}-${n + 1 + i}-0`, TEXT, { content: words })];
13415
13428
  return block;
13416
13429
  });
13417
- return [...fields, submit, ...blocks];
13430
+ return [...fields, preset.channel ? actionsRow(submit, preset.channel, `${id}-${n}`) : submit, ...blocks];
13431
+ }
13432
+ /**
13433
+ * Send and the second channel in ONE row (premium N2), the way the builder's tray builds it (iox-builder
13434
+ * `buildFormActionsRow`): the channel's address is the studio's own fact and starts EMPTY — the Publishable gate
13435
+ * blocks publishing until it is filled (`auditSite`, empty-channel). Never a number written for it.
13436
+ */
13437
+ function actionsRow(submit, channel, id) {
13438
+ const row = create(`${id}-row`, FIELD_GROUP);
13439
+ row.role = FORM_ACTIONS_ROLE;
13440
+ Object.assign(row.styleProps, { flexWrap: 'wrap', alignItems: 'center', gap: '12px' });
13441
+ const button = create(`${id}-channel`, BUTTON_BLOCK, { tag: 'a', linkType: LinkType.External, href: '', target: channelOpensNewTab(channel.kind) ? '_blank' : '_self' }, CHANNEL_STYLE);
13442
+ button.role = FORM_CHANNEL_ROLE;
13443
+ button.children = [create(`${id}-channel-0`, TEXT, { content: channel.label }, BUTTON_LABEL_STYLE)];
13444
+ row.children = [submit, button];
13445
+ return row;
13418
13446
  }
13419
13447
  /** A Form: its stable id, its preset's name and children (or the pattern's own children), and both state
13420
13448
  * blocks — never optional (forms plan D7). */
@@ -14895,7 +14923,7 @@ function wireSectionJsonSchema() {
14895
14923
  links: jsonListOf(shaped(PlanWireEntry.SlotLink, { slot: closedOrText(unique(patterns.flatMap(patternLinkSlots))), route: jsonText() })),
14896
14924
  binding: jsonText(),
14897
14925
  without: jsonListOf(closedOrText(unique(patterns.flatMap(patternOptionalParts)))),
14898
- form: orNone(FORM_PRESETS.map(p => p.id)),
14926
+ form: orNone(planFormPresetIds()),
14899
14927
  enter: orNone(ANIMATE_IDS),
14900
14928
  });
14901
14929
  }
@@ -16138,6 +16166,9 @@ var SiteFindingCode;
16138
16166
  SiteFindingCode["NoHeader"] = "no-header";
16139
16167
  SiteFindingCode["NoPhoneMenu"] = "no-phone-menu";
16140
16168
  SiteFindingCode["FormWithoutId"] = "form-without-id";
16169
+ /** Premium N2: a form's second way to reach you (WhatsApp, a phone, an email) has no address yet — the studio's own
16170
+ * fact, never written for it. */
16171
+ SiteFindingCode["EmptyChannel"] = "empty-channel";
16141
16172
  SiteFindingCode["EmailNeedsMailer"] = "email-needs-mailer";
16142
16173
  SiteFindingCode["TrackerWithoutBanner"] = "tracker-without-banner";
16143
16174
  SiteFindingCode["NoTitle"] = "no-title";
@@ -16161,6 +16192,7 @@ const FINDING_SEVERITY = {
16161
16192
  [SiteFindingCode.NoHeader]: CheckSeverity.Warn,
16162
16193
  [SiteFindingCode.NoPhoneMenu]: CheckSeverity.Warn,
16163
16194
  [SiteFindingCode.FormWithoutId]: CheckSeverity.Block,
16195
+ [SiteFindingCode.EmptyChannel]: CheckSeverity.Block,
16164
16196
  [SiteFindingCode.EmailNeedsMailer]: CheckSeverity.Warn,
16165
16197
  [SiteFindingCode.TrackerWithoutBanner]: CheckSeverity.Warn,
16166
16198
  [SiteFindingCode.NoTitle]: CheckSeverity.Warn,
@@ -16183,6 +16215,7 @@ const CHECK_OF = {
16183
16215
  [SiteFindingCode.NoHeader]: SiteCheckId.Navigation,
16184
16216
  [SiteFindingCode.NoPhoneMenu]: SiteCheckId.Navigation,
16185
16217
  [SiteFindingCode.FormWithoutId]: SiteCheckId.Forms,
16218
+ [SiteFindingCode.EmptyChannel]: SiteCheckId.Forms,
16186
16219
  [SiteFindingCode.EmailNeedsMailer]: SiteCheckId.Forms,
16187
16220
  [SiteFindingCode.TrackerWithoutBanner]: SiteCheckId.Consent,
16188
16221
  [SiteFindingCode.NoTitle]: SiteCheckId.Seo,
@@ -16274,6 +16307,9 @@ function checkForms(ctx, snapshot, pages) {
16274
16307
  walkSerialized(n.children ?? [], f => {
16275
16308
  if (f.type === FIELD_NODE_TYPE && PERSONAL_CONTROLS.has(String(f.props['control'] ?? ControlType.Text)))
16276
16309
  personal = true;
16310
+ if (f.role === FORM_CHANNEL_ROLE && !isChannelFilled(f.props['href'])) {
16311
+ add(ctx, SiteFindingCode.EmptyChannel, 'A form\'s second way to reach you has no number or address yet — only you can fill it in.', { ...where, nodeId: f.id });
16312
+ }
16277
16313
  });
16278
16314
  });
16279
16315
  return personal;