@formbox/htmx 0.4.2 → 0.4.4

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/index.d.ts CHANGED
@@ -61,6 +61,7 @@ export declare function actionValue(kind: ActionKind, path: NodePath): string;
61
61
  export declare type AddActionProperties = {
62
62
  readonly actionName: string;
63
63
  readonly addAction?: string | undefined;
64
+ readonly addId?: string | undefined;
64
65
  readonly addLabel: string;
65
66
  readonly count?: number | undefined;
66
67
  readonly countName?: string | undefined;
@@ -98,6 +99,7 @@ export declare type CheckboxListTemplateProperties = Omit<FieldTemplatePropertie
98
99
  };
99
100
 
100
101
  export declare type CheckboxOptionTemplateItem = TemplateOptionItem & {
102
+ readonly id: string;
101
103
  readonly selected: boolean;
102
104
  readonly disabled?: boolean | undefined;
103
105
  readonly hiddenInput?: HiddenTemplateInput | undefined;
@@ -114,6 +116,7 @@ export declare type CollapsibleActionProperties = {
114
116
  readonly expandedName?: string | undefined;
115
117
  readonly expandLabel?: string | undefined;
116
118
  readonly toggleAction?: string | undefined;
119
+ readonly toggleId?: string | undefined;
117
120
  };
118
121
 
119
122
  export declare function compileTemplate<TProperties extends TemplateProperties>(source: TemplateSource<TProperties>): Template<TProperties>;
@@ -125,8 +128,10 @@ export declare function countName(path: NodePath): string;
125
128
  export declare type CustomOptionFormTemplateProperties = Omit<TemplateBase<CustomOptionFormProperties>, "content" | "errors"> & {
126
129
  readonly actionName: string;
127
130
  readonly cancelLabel: string;
131
+ readonly cancelId?: string | undefined;
128
132
  readonly content: string;
129
133
  readonly errors?: string | undefined;
134
+ readonly submitId?: string | undefined;
130
135
  readonly submitLabel: string;
131
136
  };
132
137
 
@@ -171,6 +176,7 @@ export declare function fieldAttributes(path: NodePath | undefined, field: Answe
171
176
  declare type FieldTemplateProperties<TProperties> = TemplateBase<TProperties> & TemplateFieldAttributes;
172
177
 
173
178
  export declare type FileInputTemplateProperties = FieldTemplateProperties<FileInputProperties> & {
179
+ readonly clearId?: string | undefined;
174
180
  readonly clearLabel: string;
175
181
  readonly hiddenValue?: string | undefined;
176
182
  readonly clearAction: boolean;
@@ -180,6 +186,7 @@ export declare type FileInputTemplateProperties = FieldTemplateProperties<FileIn
180
186
 
181
187
  export declare type FlyoverTemplateProperties = Omit<TemplateBase<FlyoverProperties>, "children"> & {
182
188
  readonly ariaLabel: string;
189
+ readonly buttonId?: string | undefined;
183
190
  readonly children: string;
184
191
  };
185
192
 
@@ -227,6 +234,7 @@ export declare type HeaderTemplateProperties = Omit<TemplateBase<HeaderPropertie
227
234
 
228
235
  export declare type HelpTemplateProperties = Omit<TemplateBase<HelpProperties>, "children"> & {
229
236
  readonly ariaLabel: string;
237
+ readonly buttonId?: string | undefined;
230
238
  readonly children: string;
231
239
  };
232
240
 
@@ -272,6 +280,7 @@ export declare type LabelTemplateProperties = Omit<TemplateBase<LabelProperties>
272
280
  readonly flyover?: string | undefined;
273
281
  readonly media: string;
274
282
  readonly supportHyperlinks?: ReadonlyArray<NonNullable<LabelProperties["supportHyperlinks"]>[number] & {
283
+ readonly id: string;
275
284
  readonly labelHtml: string;
276
285
  }>;
277
286
  };
@@ -279,6 +288,7 @@ export declare type LabelTemplateProperties = Omit<TemplateBase<LabelProperties>
279
288
  export declare const LANGUAGE_FIELD = "fb[language]";
280
289
 
281
290
  export declare type LanguageSelectorTemplateProperties = Omit<TemplateBase<LanguageSelectorProperties>, "options"> & {
291
+ readonly id?: string | undefined;
282
292
  readonly name: string;
283
293
  readonly options: ReadonlyArray<LanguageSelectorProperties["options"][number] & {
284
294
  readonly selected: boolean;
@@ -291,6 +301,7 @@ export declare type LaunchContext = Record<string, unknown>;
291
301
 
292
302
  export declare type LegalTemplateProperties = Omit<TemplateBase<LegalProperties>, "children"> & {
293
303
  readonly ariaLabel: string;
304
+ readonly buttonId?: string | undefined;
294
305
  readonly children: string;
295
306
  };
296
307
 
@@ -302,7 +313,7 @@ export declare function loadDefaultTemplates(): Promise<RequiredTemplates>;
302
313
 
303
314
  export declare function loadTemplates(directory: string | URL): Promise<Templates>;
304
315
 
305
- export declare function mediaHtml(attachment: Attachment | undefined, fallbackLabel: string): string;
316
+ export declare function mediaHtml(attachment: Attachment | undefined, fallbackLabel: string, id?: string | undefined): string;
306
317
 
307
318
  export declare type MultiSelectInputTemplateProperties = Omit<FieldTemplateProperties<MultiSelectInputProperties>, "customOptionForm" | "options" | "selectedOptions" | "specifyOtherOption"> & {
308
319
  readonly baselineName?: string | undefined;
@@ -345,6 +356,8 @@ export declare function optionValueName(path: NodePath, token: string): string;
345
356
 
346
357
  export declare const PAGE_FIELD = "fb[page]";
347
358
 
359
+ export declare function pathControlId(token: string, path: NodePath | undefined, ...parts: Array<string | number | undefined>): string | undefined;
360
+
348
361
  export declare type ProcessResult = {
349
362
  readonly submitted: false;
350
363
  } | {
@@ -405,6 +418,7 @@ export declare type RadioButtonTemplateProperties = Omit<FieldTemplateProperties
405
418
  };
406
419
 
407
420
  export declare type RadioOptionTemplateItem = TemplateOptionItem & {
421
+ readonly id: string;
408
422
  readonly checked: boolean;
409
423
  readonly disabled?: boolean | undefined;
410
424
  };
@@ -415,6 +429,7 @@ export declare type RemoveActionProperties = {
415
429
  readonly actionName: string;
416
430
  readonly linkId?: string | undefined;
417
431
  readonly removeAction?: string | undefined;
432
+ readonly removeId?: string | undefined;
418
433
  readonly removeLabel: string;
419
434
  };
420
435
 
@@ -468,10 +483,14 @@ export declare type SliderInputTemplateProperties = FieldTemplateProperties<Slid
468
483
 
469
484
  export declare type SpinnerInputTemplateProperties = FieldTemplateProperties<SpinnerInputProperties> & NumberInputViewProperties;
470
485
 
486
+ export declare function stableId(base: string | undefined, ...parts: Array<string | number | undefined>): string | undefined;
487
+
471
488
  export declare type StackTemplateProperties = Omit<TemplateBase<StackProperties>, "children"> & {
472
489
  readonly children: string;
473
490
  };
474
491
 
492
+ export declare const SUBMIT_ATTEMPTED_FIELD = "fb[submitAttempted]";
493
+
475
494
  export declare type TabContainerTemplateProperties = Omit<TemplateBase<TabContainerProperties>, "errors" | "header" | "items"> & {
476
495
  readonly header?: string | undefined;
477
496
  readonly errors?: string | undefined;
@@ -483,7 +502,7 @@ export declare type TabContainerTemplateProperties = Omit<TemplateBase<TabContai
483
502
 
484
503
  export declare function tableColumn(column: TableColumn, renderHtml: RenderHtml): TemplateTableColumn;
485
504
 
486
- export declare function tableRow(row: TableRow, renderHtml: RenderHtml, strings: Strings): TemplateTableRow;
505
+ export declare function tableRow(row: TableRow, renderHtml: RenderHtml, strings: Strings, token: string): TemplateTableRow;
487
506
 
488
507
  export declare type TableTemplateProperties = Omit<TemplateBase<TableProperties>, "columns" | "rows"> & {
489
508
  readonly hasRowHeader: boolean;
@@ -506,7 +525,9 @@ export declare interface TemplateFieldAttributes {
506
525
  }
507
526
 
508
527
  export declare type TemplateFormPagination = Omit<FormPagination, "onNext" | "onPrev"> & {
528
+ readonly nextId?: string | undefined;
509
529
  readonly nextLabel: string;
530
+ readonly previousId?: string | undefined;
510
531
  readonly previousLabel: string;
511
532
  };
512
533
 
package/dist/index.js CHANGED
@@ -35921,6 +35921,7 @@ const Form$1 = observer(function Form2({
35921
35921
  const languageSelector = store.availableLanguages.length > 1 && store.language && onLanguageChange ? /* @__PURE__ */ jsx(
35922
35922
  ThemedLanguageSelector,
35923
35923
  {
35924
+ id: buildId(store.token, "language"),
35924
35925
  value: store.language,
35925
35926
  onChange: (value) => onLanguageChange(value),
35926
35927
  options: store.availableLanguages.map((language) => ({
@@ -35944,6 +35945,7 @@ const Form$1 = observer(function Form2({
35944
35945
  return /* @__PURE__ */ jsx(
35945
35946
  ThemedForm,
35946
35947
  {
35948
+ id: store.token,
35947
35949
  title: store.title,
35948
35950
  description: store.description,
35949
35951
  languageSelector,
@@ -36550,9 +36552,9 @@ function DateTimeDisplay({ value }) {
36550
36552
  function TimeDisplay({ value }) {
36551
36553
  return /* @__PURE__ */ jsx(Fragment, { children: value });
36552
36554
  }
36553
- function UrlDisplay({ value }) {
36555
+ function UrlDisplay({ id: id2, value }) {
36554
36556
  const { Link: Link2 } = useTheme();
36555
- return /* @__PURE__ */ jsx(Link2, { href: value, target: "_blank", rel: "noreferrer", children: value });
36557
+ return /* @__PURE__ */ jsx(Link2, { id: id2, href: value, target: "_blank", rel: "noreferrer", children: value });
36556
36558
  }
36557
36559
  function ReferenceDisplay({
36558
36560
  value
@@ -36597,6 +36599,7 @@ const VALUE_DISPLAY_BY_TYPE = {
36597
36599
  attachment: AttachmentDisplay
36598
36600
  };
36599
36601
  function ValueDisplay({
36602
+ id: id2,
36600
36603
  type,
36601
36604
  value
36602
36605
  }) {
@@ -36605,7 +36608,7 @@ function ValueDisplay({
36605
36608
  return strings2.value.unanswered;
36606
36609
  } else {
36607
36610
  const Component = VALUE_DISPLAY_BY_TYPE[type];
36608
- return /* @__PURE__ */ jsx(Component, { value });
36611
+ return /* @__PURE__ */ jsx(Component, { id: id2, value });
36609
36612
  }
36610
36613
  }
36611
36614
  const LEGACY_UNIT_TOKEN_PREFIX = "__legacy_unit__";
@@ -36647,12 +36650,20 @@ const QuantityUnitInput = observer(function QuantityUnitInput2({
36647
36650
  token: entry.token,
36648
36651
  disabled: entry.token.startsWith(LEGACY_UNIT_TOKEN_PREFIX),
36649
36652
  exclusive: false,
36650
- label: /* @__PURE__ */ jsx(OptionDisplay2, { children: /* @__PURE__ */ jsx(ValueDisplay, { type: "coding", value: entry.coding }) })
36653
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { children: /* @__PURE__ */ jsx(
36654
+ ValueDisplay,
36655
+ {
36656
+ id: buildId(id2, "option", entry.token, "display"),
36657
+ type: "coding",
36658
+ value: entry.coding
36659
+ }
36660
+ ) })
36651
36661
  }));
36652
- }, [OptionDisplay2, unitSelection.entries]);
36662
+ }, [OptionDisplay2, id2, unitSelection.entries]);
36653
36663
  const customForm = isCustomFormActive && /* @__PURE__ */ jsx(
36654
36664
  CustomForm,
36655
36665
  {
36666
+ id: buildId(id2, "custom-form"),
36656
36667
  content: answer.question.unitOption.effectiveUnitOpen === "optionsOrType" ? /* @__PURE__ */ jsx(
36657
36668
  CodingInput,
36658
36669
  {
@@ -37129,6 +37140,7 @@ const SingleDropdownSelectControl = observer(
37129
37140
  const customOptionForm = isCustomActive && store.customOptionFormState ? /* @__PURE__ */ jsx(
37130
37141
  CustomOptionForm2,
37131
37142
  {
37143
+ id: buildId(id2, "custom-form"),
37132
37144
  content: /* @__PURE__ */ jsx(
37133
37145
  Control,
37134
37146
  {
@@ -37148,11 +37160,18 @@ const SingleDropdownSelectControl = observer(
37148
37160
  const options = useMemo(() => {
37149
37161
  return store.filteredOptions.map((entry) => ({
37150
37162
  token: entry.token,
37151
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: entry.answerType, value: entry.value }) }),
37163
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(
37164
+ ValueDisplay,
37165
+ {
37166
+ id: buildId(id2, "option", entry.token, "display"),
37167
+ type: entry.answerType,
37168
+ value: entry.value
37169
+ }
37170
+ ) }),
37152
37171
  disabled: entry.disabled,
37153
37172
  exclusive: entry.exclusive === true
37154
37173
  }));
37155
- }, [OptionDisplay2, store.filteredOptions]);
37174
+ }, [OptionDisplay2, id2, store.filteredOptions]);
37156
37175
  const specifyOtherOption = store.allowCustom ? {
37157
37176
  token: store.specifyOtherToken,
37158
37177
  label: node.openLabel ?? strings2.selection.specifyOther,
@@ -37170,7 +37189,14 @@ const SingleDropdownSelectControl = observer(
37170
37189
  token: selection.token,
37171
37190
  disabled: selection.disabled,
37172
37191
  exclusive: selection.exclusive === true,
37173
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: selection.answerType, value: selection.value }) })
37192
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(
37193
+ ValueDisplay,
37194
+ {
37195
+ id: buildId(id2, "selected", selection.token, "display"),
37196
+ type: selection.answerType,
37197
+ value: selection.value
37198
+ }
37199
+ ) })
37174
37200
  };
37175
37201
  })();
37176
37202
  return /* @__PURE__ */ jsx(
@@ -37204,7 +37230,19 @@ const MultiDropdownSelectControl = observer(
37204
37230
  const CustomControl = getValueControl(store.customType);
37205
37231
  const selectedOptions = store.selectedOptions.map((selection) => ({
37206
37232
  token: selection.token,
37207
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: selection.answerType, value: selection.value }) }),
37233
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(
37234
+ ValueDisplay,
37235
+ {
37236
+ id: buildId(
37237
+ selection.answer.token,
37238
+ "selected",
37239
+ selection.token,
37240
+ "display"
37241
+ ),
37242
+ type: selection.answerType,
37243
+ value: selection.value
37244
+ }
37245
+ ) }),
37208
37246
  ariaDescribedBy: getIssueErrorId(selection.answer),
37209
37247
  errors: renderErrors(selection.answer),
37210
37248
  disabled: selection.disabled,
@@ -37214,6 +37252,7 @@ const MultiDropdownSelectControl = observer(
37214
37252
  const customOptionForm = formState ? /* @__PURE__ */ jsx(
37215
37253
  CustomOptionForm2,
37216
37254
  {
37255
+ id: buildId(formState.answer.token, "custom-form"),
37217
37256
  content: /* @__PURE__ */ jsx(
37218
37257
  CustomControl,
37219
37258
  {
@@ -37235,9 +37274,22 @@ const MultiDropdownSelectControl = observer(
37235
37274
  token: entry.token,
37236
37275
  disabled: entry.disabled,
37237
37276
  exclusive: entry.exclusive === true,
37238
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: entry.answerType, value: entry.value }) })
37277
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(
37278
+ ValueDisplay,
37279
+ {
37280
+ id: buildId(
37281
+ node.token,
37282
+ "multi-select",
37283
+ "option",
37284
+ entry.token,
37285
+ "display"
37286
+ ),
37287
+ type: entry.answerType,
37288
+ value: entry.value
37289
+ }
37290
+ ) })
37239
37291
  }));
37240
- }, [OptionDisplay2, store.filteredOptions]);
37292
+ }, [OptionDisplay2, node.token, store.filteredOptions]);
37241
37293
  const specifyOtherOption = store.allowCustom ? {
37242
37294
  token: store.specifyOtherToken,
37243
37295
  label: node.openLabel ?? strings2.selection.specifyOther,
@@ -37276,7 +37328,19 @@ const MultiListSelectControl = observer(function MultiListSelectControl2({ node
37276
37328
  const CustomControl = getValueControl(store.customType);
37277
37329
  const selectedOptions = store.selectedOptions.map((selection) => ({
37278
37330
  token: selection.token,
37279
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: selection.answerType, value: selection.value }) }),
37331
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(
37332
+ ValueDisplay,
37333
+ {
37334
+ id: buildId(
37335
+ selection.answer.token,
37336
+ "selected",
37337
+ selection.token,
37338
+ "display"
37339
+ ),
37340
+ type: selection.answerType,
37341
+ value: selection.value
37342
+ }
37343
+ ) }),
37280
37344
  ariaDescribedBy: getIssueErrorId(selection.answer),
37281
37345
  errors: renderErrors(selection.answer),
37282
37346
  disabled: selection.disabled,
@@ -37289,6 +37353,7 @@ const MultiListSelectControl = observer(function MultiListSelectControl2({ node
37289
37353
  const customOptionForm = formState ? /* @__PURE__ */ jsx(
37290
37354
  CustomOptionForm2,
37291
37355
  {
37356
+ id: buildId(formState.answer.token, "custom-form"),
37292
37357
  content: /* @__PURE__ */ jsx(
37293
37358
  CustomControl,
37294
37359
  {
@@ -37310,9 +37375,22 @@ const MultiListSelectControl = observer(function MultiListSelectControl2({ node
37310
37375
  token: entry.token,
37311
37376
  disabled: entry.disabled,
37312
37377
  exclusive: entry.exclusive === true,
37313
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: entry.answerType, value: entry.value }) })
37378
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(
37379
+ ValueDisplay,
37380
+ {
37381
+ id: buildId(
37382
+ node.token,
37383
+ "multi-select",
37384
+ "option",
37385
+ entry.token,
37386
+ "display"
37387
+ ),
37388
+ type: entry.answerType,
37389
+ value: entry.value
37390
+ }
37391
+ ) })
37314
37392
  }));
37315
- }, [OptionDisplay2, store.filteredOptions]);
37393
+ }, [OptionDisplay2, node.token, store.filteredOptions]);
37316
37394
  const specifyOtherOption = store.allowCustom ? {
37317
37395
  token: store.specifyOtherToken,
37318
37396
  label: node.openLabel ?? strings2.selection.specifyOther,
@@ -37356,6 +37434,7 @@ const SingleListSelectControl = observer(
37356
37434
  const customOptionForm = isCustomActive && store.customOptionFormState ? /* @__PURE__ */ jsx(
37357
37435
  CustomOptionForm2,
37358
37436
  {
37437
+ id: buildId(id2, "custom-form"),
37359
37438
  content: /* @__PURE__ */ jsx(
37360
37439
  Control,
37361
37440
  {
@@ -37375,11 +37454,18 @@ const SingleListSelectControl = observer(
37375
37454
  const options = useMemo(() => {
37376
37455
  return store.filteredOptions.map((entry) => ({
37377
37456
  token: entry.token,
37378
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: entry.answerType, value: entry.value }) }),
37457
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: entry.prefix, media: entry.media, children: /* @__PURE__ */ jsx(
37458
+ ValueDisplay,
37459
+ {
37460
+ id: buildId(id2, "option", entry.token, "display"),
37461
+ type: entry.answerType,
37462
+ value: entry.value
37463
+ }
37464
+ ) }),
37379
37465
  disabled: entry.disabled,
37380
37466
  exclusive: entry.exclusive === true
37381
37467
  }));
37382
- }, [OptionDisplay2, store.filteredOptions]);
37468
+ }, [OptionDisplay2, id2, store.filteredOptions]);
37383
37469
  const specifyOtherOption = store.allowCustom ? {
37384
37470
  token: store.specifyOtherToken,
37385
37471
  label: node.openLabel ?? strings2.selection.specifyOther,
@@ -37397,7 +37483,14 @@ const SingleListSelectControl = observer(
37397
37483
  token: selection.token,
37398
37484
  disabled: selection.disabled,
37399
37485
  exclusive: selection.exclusive === true,
37400
- label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: selection.answerType, value: selection.value }) })
37486
+ label: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: selection.prefix, media: selection.media, children: /* @__PURE__ */ jsx(
37487
+ ValueDisplay,
37488
+ {
37489
+ id: buildId(id2, "selected", selection.token, "display"),
37490
+ type: selection.answerType,
37491
+ value: selection.value
37492
+ }
37493
+ ) })
37401
37494
  };
37402
37495
  })();
37403
37496
  return /* @__PURE__ */ jsx(
@@ -37875,7 +37968,14 @@ const SelectionTableControl = observer(function SelectionTableControl2({
37875
37968
  })),
37876
37969
  rows: node.table.optionAxis.map((option) => ({
37877
37970
  token: option.token,
37878
- content: /* @__PURE__ */ jsx(Label2, { id: buildId(node.token, option.token), isExpanded: true, children: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: option.prefix, media: option.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: option.answerType, value: option.value }) }) }),
37971
+ content: /* @__PURE__ */ jsx(Label2, { id: buildId(node.token, option.token), isExpanded: true, children: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: option.prefix, media: option.media, children: /* @__PURE__ */ jsx(
37972
+ ValueDisplay,
37973
+ {
37974
+ id: buildId(node.token, option.token, "display"),
37975
+ type: option.answerType,
37976
+ value: option.value
37977
+ }
37978
+ ) }) }),
37879
37979
  cells: node.table.questions.map((question) => ({
37880
37980
  token: buildId(question.token, option.token),
37881
37981
  content: /* @__PURE__ */ jsx(
@@ -37895,7 +37995,14 @@ const SelectionTableControl = observer(function SelectionTableControl2({
37895
37995
  {
37896
37996
  columns: node.table.optionAxis.map((option) => ({
37897
37997
  token: option.token,
37898
- content: /* @__PURE__ */ jsx(Label2, { id: buildId(node.token, option.token), isExpanded: true, children: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: option.prefix, media: option.media, children: /* @__PURE__ */ jsx(ValueDisplay, { type: option.answerType, value: option.value }) }) })
37998
+ content: /* @__PURE__ */ jsx(Label2, { id: buildId(node.token, option.token), isExpanded: true, children: /* @__PURE__ */ jsx(OptionDisplay2, { prefix: option.prefix, media: option.media, children: /* @__PURE__ */ jsx(
37999
+ ValueDisplay,
38000
+ {
38001
+ id: buildId(node.token, option.token, "display"),
38002
+ type: option.answerType,
38003
+ value: option.value
38004
+ }
38005
+ ) }) })
37899
38006
  })),
37900
38007
  rows: node.table.questions.map((question) => ({
37901
38008
  token: question.token,
@@ -59459,6 +59566,7 @@ const templateNames = [
59459
59566
  const ACTION_FIELD = "fb[action]";
59460
59567
  const LANGUAGE_FIELD = "fb[language]";
59461
59568
  const PAGE_FIELD = "fb[page]";
59569
+ const SUBMIT_ATTEMPTED_FIELD = "fb[submitAttempted]";
59462
59570
  function valueName(path2, field) {
59463
59571
  return bracketName(["fb", "answer", ...pathParts(path2), field]);
59464
59572
  }
@@ -59511,6 +59619,24 @@ function signatureName(path2) {
59511
59619
  function actionValue(kind, path2) {
59512
59620
  return `${kind}${pathParts(path2).map((part) => `[${part}]`).join("")}`;
59513
59621
  }
59622
+ function stableId(base, ...parts) {
59623
+ if (base === void 0) {
59624
+ return void 0;
59625
+ }
59626
+ return [base, ...parts].filter((part) => part !== void 0).map(String).join("__");
59627
+ }
59628
+ function pathControlId(token, path2, ...parts) {
59629
+ if (path2 === void 0) {
59630
+ return void 0;
59631
+ }
59632
+ return stableId(
59633
+ token,
59634
+ ...path2.flatMap(
59635
+ (segment) => segment.index === void 0 ? [segment.linkId] : [segment.linkId, segment.index]
59636
+ ),
59637
+ ...parts
59638
+ );
59639
+ }
59514
59640
  function lastLinkId(path2) {
59515
59641
  return path2.at(-1)?.linkId ?? "";
59516
59642
  }
@@ -59535,7 +59661,7 @@ function isDefined(value) {
59535
59661
  function isPreservedOptionToken$1(token) {
59536
59662
  return token.includes("__custom__") || token.includes("__legacy__");
59537
59663
  }
59538
- function mediaHtml(attachment, fallbackLabel) {
59664
+ function mediaHtml(attachment, fallbackLabel, id2) {
59539
59665
  if (attachment === void 0) {
59540
59666
  return "";
59541
59667
  }
@@ -59549,12 +59675,12 @@ function mediaHtml(attachment, fallbackLabel) {
59549
59675
  return `<img${attribute("src", source)}${attribute("alt", label)}>`;
59550
59676
  }
59551
59677
  if (contentType?.startsWith("audio/")) {
59552
- return `<audio controls${attribute("src", source)}></audio>`;
59678
+ return `<audio controls${attribute("id", id2)}${attribute("src", source)}></audio>`;
59553
59679
  }
59554
59680
  if (contentType?.startsWith("video/")) {
59555
- return `<video controls${attribute("src", source)}></video>`;
59681
+ return `<video controls${attribute("id", id2)}${attribute("src", source)}></video>`;
59556
59682
  }
59557
- return `<a${attribute("href", source)} target="_blank" rel="noreferrer">${escapeHtml$1(label)}</a>`;
59683
+ return `<a${attribute("id", id2)}${attribute("href", source)} target="_blank" rel="noreferrer">${escapeHtml$1(label)}</a>`;
59558
59684
  }
59559
59685
  function formFieldsTemplate(properties) {
59560
59686
  const shortTextStyle = properties.children.includes("data-fb-label-short") ? "<style>[data-fb-label-short]{display:none}@media (max-width:40rem){[data-fb-label-full]{display:none}[data-fb-label-short]{display:inline}}</style>" : void 0;
@@ -59570,7 +59696,7 @@ function formFieldsTemplate(properties) {
59570
59696
  properties.after ?? "",
59571
59697
  properties.signature ?? "",
59572
59698
  renderPagination(properties),
59573
- `<button type="submit" name="${ACTION_FIELD}" value="submit">${escapeHtml$1(properties.submitLabel)}</button>`
59699
+ `<button type="submit"${attribute("id", stableId(properties.id, "submit"))} name="${ACTION_FIELD}" value="submit">${escapeHtml$1(properties.submitLabel)}</button>`
59574
59700
  ].join("");
59575
59701
  }
59576
59702
  function fieldAttributes$1(path2, field) {
@@ -59681,7 +59807,7 @@ function tableColumn(column, renderHtml) {
59681
59807
  widthStyle: column.width ? `width:${column.width}` : void 0
59682
59808
  };
59683
59809
  }
59684
- function tableRow(row, renderHtml, strings2) {
59810
+ function tableRow(row, renderHtml, strings2, token) {
59685
59811
  const path2 = row.path;
59686
59812
  return {
59687
59813
  token: row.token,
@@ -59693,6 +59819,7 @@ function tableRow(row, renderHtml, strings2) {
59693
59819
  actionName: ACTION_FIELD,
59694
59820
  linkId: path2 ? lastLinkId(path2) : void 0,
59695
59821
  removeAction: row.onRemove !== void 0 && row.canRemove === true && path2 ? actionValue("remove-group", path2) : void 0,
59822
+ removeId: pathControlId(token, path2, "remove-group"),
59696
59823
  removeLabel: strings2.group.removeSection,
59697
59824
  removeLabelHtml: escapeHtml$1(strings2.group.removeSection)
59698
59825
  };
@@ -59716,9 +59843,9 @@ function renderPagination(properties) {
59716
59843
  }
59717
59844
  return [
59718
59845
  "<nav>",
59719
- pagination.disabledPrev ? "" : `<button type="submit" name="${ACTION_FIELD}" value="page-prev">${escapeHtml$1(pagination.previousLabel)}</button>`,
59846
+ pagination.disabledPrev ? "" : `<button type="submit"${attribute("id", pagination.previousId)} name="${ACTION_FIELD}" value="page-prev">${escapeHtml$1(pagination.previousLabel)}</button>`,
59720
59847
  `<span>${String(pagination.current)} / ${String(pagination.total)}</span>`,
59721
- pagination.disabledNext ? "" : `<button type="submit" name="${ACTION_FIELD}" value="page-next">${escapeHtml$1(pagination.nextLabel)}</button>`,
59848
+ pagination.disabledNext ? "" : `<button type="submit"${attribute("id", pagination.nextId)} name="${ACTION_FIELD}" value="page-next">${escapeHtml$1(pagination.nextLabel)}</button>`,
59722
59849
  "</nav>"
59723
59850
  ].join("");
59724
59851
  }
@@ -59956,10 +60083,15 @@ async function processStoreFormDataAsync(store, formData) {
59956
60083
  await applySubmittedState(store, formData);
59957
60084
  const actions = getActions(formData);
59958
60085
  actions.forEach((action2) => applyAction(store, action2));
59959
- if (actions.includes("submit")) {
60086
+ const submitted = actions.includes("submit");
60087
+ if (submitted || readSubmitAttempted(formData)) {
60088
+ const valid = store.validateAll();
60089
+ if (!submitted) {
60090
+ return { submitted: false };
60091
+ }
59960
60092
  return {
59961
60093
  submitted: true,
59962
- valid: store.validateAll()
60094
+ valid
59963
60095
  };
59964
60096
  }
59965
60097
  return { submitted: false };
@@ -61037,6 +61169,9 @@ function readBoolean(path2, formData) {
61037
61169
  function getActions(formData) {
61038
61170
  return formData.getAll(ACTION_FIELD).filter((value) => typeof value === "string");
61039
61171
  }
61172
+ function readSubmitAttempted(formData) {
61173
+ return getLastString(formData, SUBMIT_ATTEMPTED_FIELD) === "true";
61174
+ }
61040
61175
  function getLastString(formData, name) {
61041
61176
  return getStrings(formData, name).at(-1);
61042
61177
  }
@@ -61154,7 +61289,7 @@ function stripHtmlTag(ssr) {
61154
61289
  return ssr.replaceAll(/<x-html\b[^>]*>/gu, "").replaceAll("</x-html>", "");
61155
61290
  }
61156
61291
  function AnswerList(properties) {
61157
- const { templates } = useHtmxTheme();
61292
+ const { templates, token } = useHtmxTheme();
61158
61293
  const renderHtml = useHtml();
61159
61294
  const strings2 = useStrings();
61160
61295
  const children = Children.toArray(properties.children);
@@ -61164,6 +61299,7 @@ function AnswerList(properties) {
61164
61299
  children: renderHtml(properties.children),
61165
61300
  actionName: ACTION_FIELD,
61166
61301
  addAction: properties.onAdd !== void 0 && properties.canAdd && path2 ? actionValue("add-answer", path2) : void 0,
61302
+ addId: pathControlId(token, path2, "add-answer"),
61167
61303
  addLabel: strings2.selection.addAnother,
61168
61304
  count: path2 ? children.length : void 0,
61169
61305
  countName: path2 ? countName(path2) : void 0,
@@ -61172,7 +61308,7 @@ function AnswerList(properties) {
61172
61308
  });
61173
61309
  }
61174
61310
  function AnswerScaffold(properties) {
61175
- const { templates } = useHtmxTheme();
61311
+ const { templates, token } = useHtmxTheme();
61176
61312
  const renderHtml = useHtml();
61177
61313
  const strings2 = useStrings();
61178
61314
  const path2 = properties.path;
@@ -61184,6 +61320,7 @@ function AnswerScaffold(properties) {
61184
61320
  actionName: ACTION_FIELD,
61185
61321
  linkId: path2 ? lastLinkId(path2) : void 0,
61186
61322
  removeAction: properties.onRemove !== void 0 && properties.canRemove && path2 ? actionValue("remove-answer", path2) : void 0,
61323
+ removeId: pathControlId(token, path2, "remove-answer"),
61187
61324
  removeLabel: strings2.group.removeSection
61188
61325
  });
61189
61326
  }
@@ -61253,6 +61390,7 @@ function CheckboxList(properties) {
61253
61390
  orientation: properties.orientation,
61254
61391
  options: options.map((option) => ({
61255
61392
  ...option,
61393
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61256
61394
  selected: selectedTokens.has(option.token),
61257
61395
  disabled: Boolean(properties.disabled) || option.disabled,
61258
61396
  hiddenInput: name && preservedByToken.has(option.token) ? { name, value: option.token } : void 0
@@ -61272,15 +61410,19 @@ function CheckboxList(properties) {
61272
61410
  });
61273
61411
  }
61274
61412
  function CustomOptionForm(properties) {
61275
- const { templates } = useHtmxTheme();
61413
+ const { templates, token } = useHtmxTheme();
61276
61414
  const renderHtml = useHtml();
61277
61415
  const strings2 = useStrings();
61416
+ const id2 = properties.id ?? stableId(token, "custom-option-form");
61278
61417
  return renderTemplate(templates.CustomOptionForm, {
61418
+ id: id2,
61279
61419
  canSubmit: properties.canSubmit,
61280
61420
  actionName: ACTION_FIELD,
61281
61421
  cancelLabel: strings2.dialog.cancel,
61422
+ cancelId: stableId(id2, "cancel"),
61282
61423
  content: renderHtml(properties.content),
61283
61424
  errors: renderHtml(properties.errors),
61425
+ submitId: stableId(id2, "submit"),
61284
61426
  submitLabel: strings2.dialog.submit
61285
61427
  });
61286
61428
  }
@@ -61361,6 +61503,7 @@ function FileInput(properties) {
61361
61503
  disabled: properties.disabled,
61362
61504
  accept: properties.accept,
61363
61505
  clearLabel: strings2.file.clearAction,
61506
+ clearId: stableId(properties.id, "clear"),
61364
61507
  hiddenValue: properties.value && attributes.name ? JSON.stringify(properties.value) : void 0,
61365
61508
  clearAction: properties.value !== void 0 && attributes.name !== void 0 && !properties.disabled,
61366
61509
  dataLinkId: attributes["data-fb-link-id"],
@@ -61374,6 +61517,7 @@ function Flyover(properties) {
61374
61517
  const strings2 = useStrings();
61375
61518
  return renderTemplate(templates.Flyover, {
61376
61519
  id: properties.id,
61520
+ buttonId: stableId(properties.id, "button"),
61377
61521
  ariaLabel: strings2.aria.flyover,
61378
61522
  children: renderHtml(properties.children)
61379
61523
  });
@@ -61387,18 +61531,22 @@ function Footer(properties) {
61387
61531
  });
61388
61532
  }
61389
61533
  function Form(properties) {
61390
- const { action: action2, hiddenFields, templates } = useHtmxTheme();
61534
+ const { action: action2, hiddenFields, templates, token } = useHtmxTheme();
61391
61535
  const renderHtml = useHtml();
61392
61536
  const strings2 = useStrings();
61537
+ const id2 = properties.id ?? token;
61393
61538
  const pagination = properties.pagination ? {
61394
61539
  current: properties.pagination.current,
61395
61540
  total: properties.pagination.total,
61396
61541
  disabledPrev: properties.pagination.disabledPrev,
61397
61542
  disabledNext: properties.pagination.disabledNext,
61543
+ nextId: stableId(id2, "pagination", "next"),
61398
61544
  nextLabel: strings2.pagination.next,
61545
+ previousId: stableId(id2, "pagination", "previous"),
61399
61546
  previousLabel: strings2.pagination.previous
61400
61547
  } : void 0;
61401
61548
  const formProperties = {
61549
+ id: id2,
61402
61550
  title: properties.title,
61403
61551
  description: properties.description,
61404
61552
  customExtensions: properties.customExtensions,
@@ -61414,12 +61562,12 @@ function Form(properties) {
61414
61562
  const fields = [hiddenFields, formFieldsTemplate(formProperties)].join("");
61415
61563
  return renderTemplate(templates.Form, {
61416
61564
  ...formProperties,
61417
- attributes: defaultAttributes(action2),
61565
+ attributes: { id: id2, ...defaultAttributes(action2) },
61418
61566
  fields
61419
61567
  });
61420
61568
  }
61421
61569
  function GroupList(properties) {
61422
- const { templates } = useHtmxTheme();
61570
+ const { templates, token } = useHtmxTheme();
61423
61571
  const renderHtml = useHtml();
61424
61572
  const strings2 = useStrings();
61425
61573
  const groups2 = Children.toArray(properties.children);
@@ -61434,6 +61582,7 @@ function GroupList(properties) {
61434
61582
  children: renderHtml(properties.children),
61435
61583
  actionName: ACTION_FIELD,
61436
61584
  addAction: properties.onAdd !== void 0 && properties.canAdd && path2 ? actionValue("add-group", path2) : void 0,
61585
+ addId: pathControlId(token, path2, "add-group"),
61437
61586
  addLabel: strings2.group.addSection,
61438
61587
  count: path2 ? count : void 0,
61439
61588
  countName: path2 ? countName(path2) : void 0,
@@ -61441,7 +61590,7 @@ function GroupList(properties) {
61441
61590
  });
61442
61591
  }
61443
61592
  function GroupScaffold(properties) {
61444
- const { templates } = useHtmxTheme();
61593
+ const { templates, token } = useHtmxTheme();
61445
61594
  const renderHtml = useHtml();
61446
61595
  const strings2 = useStrings();
61447
61596
  const path2 = properties.path;
@@ -61464,8 +61613,10 @@ function GroupScaffold(properties) {
61464
61613
  expandedValue: expandedName$1 ? String(Boolean(properties.isExpanded)) : void 0,
61465
61614
  expandLabel: strings2.collapsible.expand,
61466
61615
  removeAction: properties.onRemove !== void 0 && properties.canRemove && path2 ? actionValue("remove-group", path2) : void 0,
61616
+ removeId: pathControlId(token, path2, "remove-group"),
61467
61617
  removeLabel: strings2.group.removeSection,
61468
61618
  toggleAction,
61619
+ toggleId: pathControlId(token, path2, "toggle-expanded"),
61469
61620
  summaryLabel: properties.isExpanded ? strings2.collapsible.collapse : strings2.collapsible.expand,
61470
61621
  expandedChildren: properties.isExpanded ? renderHtml(properties.children) : ""
61471
61622
  });
@@ -61484,6 +61635,7 @@ function Help(properties) {
61484
61635
  const strings2 = useStrings();
61485
61636
  return renderTemplate(templates.Help, {
61486
61637
  id: properties.id,
61638
+ buttonId: stableId(properties.id, "button"),
61487
61639
  ariaLabel: strings2.aria.help,
61488
61640
  children: renderHtml(properties.children)
61489
61641
  });
@@ -61536,18 +61688,26 @@ function Label(properties) {
61536
61688
  help: renderHtml(properties.help),
61537
61689
  legal: renderHtml(properties.legal),
61538
61690
  flyover: renderHtml(properties.flyover),
61539
- media: mediaHtml(properties.media, strings2.inputs.attachmentLabel),
61691
+ media: mediaHtml(
61692
+ properties.media,
61693
+ strings2.inputs.attachmentLabel,
61694
+ stableId(properties.id, "media")
61695
+ ),
61540
61696
  ...properties.supportHyperlinks ? {
61541
- supportHyperlinks: properties.supportHyperlinks.map((link) => ({
61542
- ...link,
61543
- labelHtml: escapeHtml$1(link.label || link.href)
61544
- }))
61697
+ supportHyperlinks: properties.supportHyperlinks.map(
61698
+ (link, index) => ({
61699
+ ...link,
61700
+ id: stableId(properties.id, "support", index) ?? String(index),
61701
+ labelHtml: escapeHtml$1(link.label || link.href)
61702
+ })
61703
+ )
61545
61704
  } : {}
61546
61705
  });
61547
61706
  }
61548
61707
  function LanguageSelector(properties) {
61549
- const { templates } = useHtmxTheme();
61708
+ const { templates, token } = useHtmxTheme();
61550
61709
  return renderTemplate(templates.LanguageSelector, {
61710
+ id: properties.id ?? stableId(token, "language"),
61551
61711
  value: properties.value,
61552
61712
  name: LANGUAGE_FIELD,
61553
61713
  options: properties.options.map((option) => ({
@@ -61562,6 +61722,7 @@ function Legal(properties) {
61562
61722
  const strings2 = useStrings();
61563
61723
  return renderTemplate(templates.Legal, {
61564
61724
  id: properties.id,
61725
+ buttonId: stableId(properties.id, "button"),
61565
61726
  ariaLabel: strings2.aria.legal,
61566
61727
  children: renderHtml(properties.children)
61567
61728
  });
@@ -61570,6 +61731,7 @@ function Link(properties) {
61570
61731
  const { templates } = useHtmxTheme();
61571
61732
  const renderHtml = useHtml();
61572
61733
  return renderTemplate(templates.Link, {
61734
+ id: properties.id,
61573
61735
  href: properties.href,
61574
61736
  target: properties.target,
61575
61737
  rel: properties.rel,
@@ -61628,6 +61790,7 @@ function MultiSelectInput(properties) {
61628
61790
  baselineName,
61629
61791
  options: options.map((option) => ({
61630
61792
  ...option,
61793
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61631
61794
  selected: selectedTokens.has(option.token),
61632
61795
  disabled: Boolean(properties.disabled) || option.disabled,
61633
61796
  hiddenInput: name && preservedByToken.has(option.token) ? { name, value: option.token } : void 0
@@ -61673,7 +61836,7 @@ function OptionsLoading(properties) {
61673
61836
  });
61674
61837
  }
61675
61838
  function QuestionScaffold(properties) {
61676
- const { templates } = useHtmxTheme();
61839
+ const { templates, token } = useHtmxTheme();
61677
61840
  const renderHtml = useHtml();
61678
61841
  const strings2 = useStrings();
61679
61842
  const path2 = properties.path;
@@ -61695,6 +61858,7 @@ function QuestionScaffold(properties) {
61695
61858
  expandedValue: expandedName$1 ? String(Boolean(properties.isExpanded)) : void 0,
61696
61859
  expandLabel: strings2.collapsible.expand,
61697
61860
  toggleAction,
61861
+ toggleId: pathControlId(token, path2, "toggle-expanded"),
61698
61862
  summaryLabel: properties.isExpanded ? strings2.collapsible.collapse : strings2.collapsible.expand,
61699
61863
  expandedChildren: properties.isExpanded ? renderHtml(properties.children) : ""
61700
61864
  });
@@ -61748,6 +61912,7 @@ function RadioButtonList(properties) {
61748
61912
  hiddenValue: (preserveValue || mirrorValue) && selectedToken && attributes.name ? selectedToken : void 0,
61749
61913
  options: options.map((option) => ({
61750
61914
  ...option,
61915
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61751
61916
  checked: option.token === selectedToken,
61752
61917
  disabled: Boolean(properties.disabled) || Boolean(option.disabled)
61753
61918
  })),
@@ -61869,10 +62034,12 @@ function TabContainer(properties) {
61869
62034
  });
61870
62035
  }
61871
62036
  function Table(properties) {
61872
- const { templates } = useHtmxTheme();
62037
+ const { templates, token } = useHtmxTheme();
61873
62038
  const renderHtml = useHtml();
61874
62039
  const strings2 = useStrings();
61875
- const rows = properties.rows.map((row) => tableRow(row, renderHtml, strings2));
62040
+ const rows = properties.rows.map(
62041
+ (row) => tableRow(row, renderHtml, strings2, token)
62042
+ );
61876
62043
  return renderTemplate(templates.Table, {
61877
62044
  columns: properties.columns.map(
61878
62045
  (column) => tableColumn(column, renderHtml)
@@ -61984,6 +62151,7 @@ function renderStoreFields(store, templates, action2) {
61984
62151
  const theme2 = createTheme();
61985
62152
  const htmxTheme = {
61986
62153
  templates,
62154
+ token: store.token,
61987
62155
  hiddenFields: renderHiddenFieldsForStore(store),
61988
62156
  activeTabValue: activeTab,
61989
62157
  action: action2
@@ -62035,6 +62203,7 @@ function renderHiddenFieldsForStore(store) {
62035
62203
  ...store.footerNodes
62036
62204
  ]);
62037
62205
  return [
62206
+ store.isSubmitAttempted ? `<input type="hidden" name="${SUBMIT_ATTEMPTED_FIELD}" value="true">` : "",
62038
62207
  store.nodes.map(
62039
62208
  (node) => renderHiddenFieldsForNode(node, [], renderedNodes.has(node))
62040
62209
  ).join(""),
@@ -4,7 +4,7 @@ Template: AnswerList
4
4
  Inputs:
5
5
  - children: rendered repeated answer rows.
6
6
  - hasCount/countName/count: hidden occurrence count state.
7
- - actionName/addAction/addLabel: add-answer submit action.
7
+ - actionName/addAction/addId/addLabel: add-answer submit action.
8
8
  - linkId: current question link id for data attributes.
9
9
  - path: renderer node path, when present.
10
10
  --}}
@@ -13,5 +13,5 @@ Inputs:
13
13
  {{/if}}
14
14
  {{{children}}}
15
15
  {{#if addAction}}
16
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="add-action"{{{attr "name" actionName}}}{{{attr "value" addAction}}}>{{addLabel}}</button>
16
+ <button type="submit"{{{attr "id" addId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="add-action"{{{attr "name" actionName}}}{{{attr "value" addAction}}}>{{addLabel}}</button>
17
17
  {{/if}}
@@ -5,7 +5,7 @@ Inputs:
5
5
  - control: rendered answer control.
6
6
  - errors: rendered answer errors.
7
7
  - children: rendered answer child items.
8
- - actionName/removeAction/removeLabel: remove-answer submit action.
8
+ - actionName/removeAction/removeId/removeLabel: remove-answer submit action.
9
9
  - linkId: current question link id for data attributes.
10
10
  - path/canRemove: repeat answer state used to derive removeAction.
11
11
  --}}
@@ -14,6 +14,6 @@ Inputs:
14
14
  {{{errors}}}
15
15
  {{{children}}}
16
16
  {{#if removeAction}}
17
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
17
+ <button type="submit"{{{attr "id" removeId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
18
18
  {{/if}}
19
19
  </div>
@@ -3,7 +3,7 @@ Template: CheckboxList
3
3
 
4
4
  Inputs:
5
5
  - id: fieldset id.
6
- - options: items with token, label, selected, disabled, and hiddenInput.
6
+ - options: items with id, token, label, selected, disabled, and hiddenInput.
7
7
  - selectedOptions/selectedName: selected option state and hidden selected-token field name.
8
8
  - hiddenInputs/trailingHiddenInputs: hidden values that preserve disabled or off-list selections.
9
9
  - specifyOtherOption/customOptionForm: custom option entry controls.
@@ -20,7 +20,7 @@ Inputs:
20
20
  {{#if hiddenInput}}
21
21
  <input type="hidden"{{{attr "name" hiddenInput.name}}}{{{attr "value" hiddenInput.value}}}>
22
22
  {{/if}}
23
- <label><input{{{fieldAttributes ..}}} type="checkbox"{{{attr "value" token}}}{{{attr "checked" selected}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
23
+ <label><input{{{fieldAttributes ..}}}{{{attr "id" id}}} type="checkbox"{{{attr "value" token}}}{{{attr "checked" selected}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
24
24
  {{/each}}
25
25
  {{#each trailingHiddenInputs}}
26
26
  <input type="hidden"{{{attr "name" name}}}{{{attr "value" value}}}>
@@ -5,12 +5,14 @@ Inputs:
5
5
  - content: rendered custom option fields.
6
6
  - errors: rendered validation errors.
7
7
  - actionName: submit action field name.
8
- - submitLabel/cancelLabel: button labels.
8
+ - id: fieldset id.
9
+ - submitId/submitLabel: submit button id and label.
10
+ - cancelId/cancelLabel: cancel button id and label.
9
11
  - canSubmit: source theme state, if supplied.
10
12
  --}}
11
- <fieldset>
13
+ <fieldset{{{attr "id" id}}}>
12
14
  {{{content}}}
13
15
  {{{errors}}}
14
- <button type="submit"{{{attr "name" actionName}}} value="submit-custom">{{submitLabel}}</button>
15
- <button type="submit"{{{attr "name" actionName}}} value="cancel-custom">{{cancelLabel}}</button>
16
+ <button type="submit"{{{attr "id" submitId}}}{{{attr "name" actionName}}} value="submit-custom">{{submitLabel}}</button>
17
+ <button type="submit"{{{attr "id" cancelId}}}{{{attr "name" actionName}}} value="cancel-custom">{{cancelLabel}}</button>
16
18
  </fieldset>
@@ -4,7 +4,7 @@ Template: FileInput
4
4
  Inputs:
5
5
  - id: control id.
6
6
  - value/hiddenValue: current attachment and hidden JSON mirror.
7
- - clearAction/clearLabel: clear-file submit control state.
7
+ - clearAction/clearId/clearLabel: clear-file submit control state.
8
8
  - dataLinkId/hxInclude: clear button HTMX attributes.
9
9
  - disabled/accept: native file input state.
10
10
  - ariaLabelledBy/ariaDescribedBy: accessibility references.
@@ -16,5 +16,5 @@ Inputs:
16
16
  {{/if}}
17
17
  <input{{{fieldAttributes}}}{{{attr "id" id}}} type="file"{{{attr "disabled" disabled}}}{{{attr "accept" accept}}}{{{attr "aria-labelledby" ariaLabelledBy}}}{{{attr "aria-describedby" ariaDescribedBy}}}>
18
18
  {{#if clearAction}}
19
- <button type="submit"{{{attr "data-fb-link-id" dataLinkId}}} data-fb-field="value"{{{attr "name" name}}} value=""{{{attr "hx-include" hxInclude}}}>{{clearLabel}}</button>
19
+ <button type="submit"{{{attr "id" clearId}}}{{{attr "data-fb-link-id" dataLinkId}}} data-fb-field="value"{{{attr "name" name}}} value=""{{{attr "hx-include" hxInclude}}}>{{clearLabel}}</button>
20
20
  {{/if}}
@@ -4,9 +4,9 @@ Template: Flyover
4
4
  Inputs:
5
5
  - id: tooltip id.
6
6
  - children: rendered flyover content.
7
- - ariaLabel: button accessible label.
7
+ - buttonId/ariaLabel: button id and accessible label.
8
8
  --}}
9
9
  <span>
10
- <button type="button"{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>i</button>
10
+ <button type="button"{{{attr "id" buttonId}}}{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>i</button>
11
11
  <span{{{attr "id" id}}} role="tooltip">{{{children}}}</span>
12
12
  </span>
@@ -5,7 +5,7 @@ Inputs:
5
5
  - linkId: repeated group link id.
6
6
  - header/errors/children: rendered group list sections.
7
7
  - hasCount/countName/count: hidden occurrence count state.
8
- - actionName/addAction/addLabel: add-group submit action.
8
+ - actionName/addAction/addId/addLabel: add-group submit action.
9
9
  - path/canAdd: repeat group state used to derive addAction.
10
10
  - customExtensions: source extension values, if supplied.
11
11
  --}}
@@ -17,6 +17,6 @@ Inputs:
17
17
  {{{errors}}}
18
18
  {{{children}}}
19
19
  {{#if addAction}}
20
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="add-action"{{{attr "name" actionName}}}{{{attr "value" addAction}}}>{{addLabel}}</button>
20
+ <button type="submit"{{{attr "id" addId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="add-action"{{{attr "name" actionName}}}{{{attr "value" addAction}}}>{{addLabel}}</button>
21
21
  {{/if}}
22
22
  </section>
@@ -5,8 +5,8 @@ Inputs:
5
5
  - linkId: group link id.
6
6
  - header/children/expandedChildren/errors/signature: rendered group sections.
7
7
  - isExpandable/isExpanded/expandedName/expandedValue: collapsible group state.
8
- - summaryLabel/collapseLabel/expandLabel/toggleAction: collapsible submit control state.
9
- - actionName/removeAction/removeLabel: remove-group submit action.
8
+ - summaryLabel/collapseLabel/expandLabel/toggleAction/toggleId: collapsible submit control state.
9
+ - actionName/removeAction/removeId/removeLabel: remove-group submit action.
10
10
  - path/canRemove: repeat group state used to derive removeAction.
11
11
  - customExtensions: source extension values, if supplied.
12
12
  --}}
@@ -17,20 +17,20 @@ Inputs:
17
17
  <input type="hidden"{{{attr "name" expandedName}}}{{{attr "value" expandedValue}}}>
18
18
  {{/if}}
19
19
  <details{{{attr "data-fb-collapsible" linkId}}}{{{attr "open" isExpanded}}}>
20
- <summary>{{#if toggleAction}}<button type="submit"{{{attr "name" actionName}}}{{{attr "value" toggleAction}}}>{{summaryLabel}}</button>{{else}}{{summaryLabel}}{{/if}}</summary>
20
+ <summary>{{#if toggleAction}}<button type="submit"{{{attr "id" toggleId}}}{{{attr "name" actionName}}}{{{attr "value" toggleAction}}}>{{summaryLabel}}</button>{{else}}{{summaryLabel}}{{/if}}</summary>
21
21
  {{{expandedChildren}}}
22
22
  </details>
23
23
  {{{errors}}}
24
24
  {{{signature}}}
25
25
  {{#if removeAction}}
26
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
26
+ <button type="submit"{{{attr "id" removeId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
27
27
  {{/if}}
28
28
  {{else}}
29
29
  {{{errors}}}
30
30
  {{{children}}}
31
31
  {{{signature}}}
32
32
  {{#if removeAction}}
33
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
33
+ <button type="submit"{{{attr "id" removeId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{removeLabel}}</button>
34
34
  {{/if}}
35
35
  {{/if}}
36
36
  </fieldset>
@@ -4,9 +4,9 @@ Template: Help
4
4
  Inputs:
5
5
  - id: tooltip id.
6
6
  - children: rendered help content.
7
- - ariaLabel: button accessible label.
7
+ - buttonId/ariaLabel: button id and accessible label.
8
8
  --}}
9
9
  <span>
10
- <button type="button"{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>?</button>
10
+ <button type="button"{{{attr "id" buttonId}}}{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>?</button>
11
11
  <span{{{attr "id" id}}} role="tooltip">{{{children}}}</span>
12
12
  </span>
@@ -6,18 +6,18 @@ Inputs:
6
6
  - as/isLegend/isText: rendered label element kind.
7
7
  - content: rendered label text, prefix, required marker, and help/legal/flyover html.
8
8
  - children/prefix/required/shortText: source label text state.
9
- - supportHyperlinks: links with href and labelHtml.
9
+ - supportHyperlinks: links with id, href, and labelHtml.
10
10
  - media: rendered attachment media html.
11
11
  - help/legal/flyover: rendered extension slots.
12
12
  - isExpanded: source expansion state, if supplied.
13
13
  - attachmentLabel: accessible text used while rendering media.
14
14
  --}}
15
15
  {{#if isLegend}}
16
- <legend{{{attr "id" id}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</legend>
16
+ <legend{{{attr "id" id}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "id" id}}}{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</legend>
17
17
  {{else}}
18
18
  {{#if isText}}
19
- <span{{{attr "id" id}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</span>
19
+ <span{{{attr "id" id}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "id" id}}}{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</span>
20
20
  {{else}}
21
- <label{{{attr "id" id}}}{{{attr "for" htmlFor}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</label>
21
+ <label{{{attr "id" id}}}{{{attr "for" htmlFor}}}{{{attr "data-short-text" shortText}}}>{{{content}}}{{#if supportHyperlinks.length}}<div>{{#each supportHyperlinks}}<a{{{attr "id" id}}}{{{attr "href" href}}} target="_blank" rel="noreferrer">{{{labelHtml}}}</a>{{/each}}</div>{{/if}}{{{media}}}</label>
22
22
  {{/if}}
23
23
  {{/if}}
@@ -2,11 +2,12 @@
2
2
  Template: LanguageSelector
3
3
 
4
4
  Inputs:
5
+ - id: select id.
5
6
  - name: generated language field name.
6
7
  - value: current language code.
7
8
  - options: language options with value, label, and selected.
8
9
  --}}
9
- <select{{{attr "name" name}}}>
10
+ <select{{{attr "id" id}}}{{{attr "name" name}}}>
10
11
  {{#each options}}
11
12
  <option{{{attr "value" value}}}{{{attr "selected" selected}}}>{{label}}</option>
12
13
  {{/each}}
@@ -4,9 +4,9 @@ Template: Legal
4
4
  Inputs:
5
5
  - id: dialog id.
6
6
  - children: rendered legal content.
7
- - ariaLabel: button accessible label.
7
+ - buttonId/ariaLabel: button id and accessible label.
8
8
  --}}
9
9
  <span>
10
- <button type="button"{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>!</button>
10
+ <button type="button"{{{attr "id" buttonId}}}{{{attr "aria-describedby" id}}}{{{attr "aria-label" ariaLabel}}}>!</button>
11
11
  <span{{{attr "id" id}}} role="dialog">{{{children}}}</span>
12
12
  </span>
@@ -2,8 +2,9 @@
2
2
  Template: Link
3
3
 
4
4
  Inputs:
5
+ - id: link id.
5
6
  - href: link target.
6
7
  - target/rel: link browsing attributes.
7
8
  - children: rendered link content.
8
9
  --}}
9
- <a{{{attr "href" href}}}{{{attr "target" target}}}{{{attr "rel" rel}}}>{{{children}}}</a>
10
+ <a{{{attr "id" id}}}{{{attr "href" href}}}{{{attr "target" target}}}{{{attr "rel" rel}}}>{{{children}}}</a>
@@ -3,7 +3,7 @@ Template: MultiSelectInput
3
3
 
4
4
  Inputs:
5
5
  - id: fieldset id.
6
- - options: items with token, label, selected, disabled, and hiddenInput.
6
+ - options: items with id, token, label, selected, disabled, and hiddenInput.
7
7
  - selectedOptions/selectedName: selected option state and hidden selected-token field name.
8
8
  - hiddenInputs/trailingHiddenInputs: hidden values that preserve disabled or off-list selections.
9
9
  - searchQuery/searchName/searchLabel: server-side option search state.
@@ -21,7 +21,7 @@ Inputs:
21
21
  {{#if hiddenInput}}
22
22
  <input type="hidden"{{{attr "name" hiddenInput.name}}}{{{attr "value" hiddenInput.value}}}>
23
23
  {{/if}}
24
- <label><input{{{fieldAttributes ..}}} type="checkbox"{{{attr "value" token}}}{{{attr "checked" selected}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
24
+ <label><input{{{fieldAttributes ..}}}{{{attr "id" id}}} type="checkbox"{{{attr "value" token}}}{{{attr "checked" selected}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
25
25
  {{/each}}
26
26
  {{#each trailingHiddenInputs}}
27
27
  <input type="hidden"{{{attr "name" name}}}{{{attr "value" value}}}>
@@ -5,7 +5,7 @@ Inputs:
5
5
  - linkId: question link id.
6
6
  - header/children/expandedChildren/errors/signature: rendered question sections.
7
7
  - isExpandable/isExpanded/expandedName/expandedValue: collapsible question state.
8
- - summaryLabel/collapseLabel/expandLabel/toggleAction: collapsible submit control state.
8
+ - summaryLabel/collapseLabel/expandLabel/toggleAction/toggleId: collapsible submit control state.
9
9
  - actionName: submit action field name.
10
10
  - path: renderer node path, when present.
11
11
  - customExtensions: source extension values, if supplied.
@@ -17,7 +17,7 @@ Inputs:
17
17
  <input type="hidden"{{{attr "name" expandedName}}}{{{attr "value" expandedValue}}}>
18
18
  {{/if}}
19
19
  <details{{{attr "data-fb-collapsible" linkId}}}{{{attr "open" isExpanded}}}>
20
- <summary>{{#if toggleAction}}<button type="submit"{{{attr "name" actionName}}}{{{attr "value" toggleAction}}}>{{summaryLabel}}</button>{{else}}{{summaryLabel}}{{/if}}</summary>
20
+ <summary>{{#if toggleAction}}<button type="submit"{{{attr "id" toggleId}}}{{{attr "name" actionName}}}{{{attr "value" toggleAction}}}>{{summaryLabel}}</button>{{else}}{{summaryLabel}}{{/if}}</summary>
21
21
  {{{expandedChildren}}}
22
22
  </details>
23
23
  {{{errors}}}
@@ -3,7 +3,7 @@ Template: RadioButtonList
3
3
 
4
4
  Inputs:
5
5
  - id: fieldset id.
6
- - options: items with token, label, checked, and disabled.
6
+ - options: items with id, token, label, checked, and disabled.
7
7
  - selectedOption: current selected option state.
8
8
  - hiddenValue: hidden mirror value for disabled or preserved selections.
9
9
  - baselineName/baselineValue: hidden original value for protected selection state.
@@ -21,7 +21,7 @@ Inputs:
21
21
  <input type="hidden"{{{attr "name" baselineName}}}{{{attr "value" baselineValue}}}>
22
22
  {{/if}}
23
23
  {{#each options}}
24
- <label><input{{{fieldAttributes ..}}} type="radio"{{{attr "value" token}}}{{{attr "checked" checked}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
24
+ <label><input{{{fieldAttributes ..}}}{{{attr "id" id}}} type="radio"{{{attr "value" token}}}{{{attr "checked" checked}}}{{{attr "disabled" disabled}}}{{{attr "aria-describedby" ../ariaDescribedBy}}}>{{{label}}}</label>
25
25
  {{/each}}
26
26
  {{{customOptionForm}}}
27
27
  </fieldset>
@@ -4,7 +4,7 @@ Template: Table
4
4
  Inputs:
5
5
  - hasRowHeader: true when rows need a header column.
6
6
  - columns: column items with token, content, errors, width, widthStyle, and isLoading.
7
- - rows: row items with token, content, errors, cells, remove action fields, and isLoading.
7
+ - rows: row items with token, content, errors, cells, remove action fields, removeId, and isLoading.
8
8
  - rows[].cells: cell items with token and content.
9
9
  --}}
10
10
  <table>
@@ -26,7 +26,7 @@ Inputs:
26
26
  {{{content}}}
27
27
  {{{errors}}}
28
28
  {{#if removeAction}}
29
- <button type="submit"{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{{removeLabelHtml}}}</button>
29
+ <button type="submit"{{{attr "id" removeId}}}{{{attr "data-fb-link-id" linkId}}} data-fb-field="remove-action"{{{attr "name" actionName}}}{{{attr "value" removeAction}}}>{{{removeLabelHtml}}}</button>
30
30
  {{/if}}
31
31
  </th>
32
32
  {{/if}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formbox/htmx",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Server-rendered HTMX HTML renderer for Formbox FHIR Questionnaires",
5
5
  "private": false,
6
6
  "type": "module",
@@ -30,10 +30,10 @@
30
30
  "mobx-utils": "^6.1.1",
31
31
  "react": "^19.2.3",
32
32
  "react-dom": "^19.2.3",
33
- "@formbox/fhir": "0.4.0",
34
- "@formbox/renderer": "0.4.0",
35
- "@formbox/strings": "0.4.0",
36
- "@formbox/theme": "0.4.0"
33
+ "@formbox/renderer": "0.4.1",
34
+ "@formbox/strings": "0.4.1",
35
+ "@formbox/theme": "0.4.1",
36
+ "@formbox/fhir": "0.4.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@playwright/test": "^1.60.0",