@formbox/htmx 0.4.3 → 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,6 +483,8 @@ 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
  };
@@ -485,7 +502,7 @@ export declare type TabContainerTemplateProperties = Omit<TemplateBase<TabContai
485
502
 
486
503
  export declare function tableColumn(column: TableColumn, renderHtml: RenderHtml): TemplateTableColumn;
487
504
 
488
- 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;
489
506
 
490
507
  export declare type TableTemplateProperties = Omit<TemplateBase<TableProperties>, "columns" | "rows"> & {
491
508
  readonly hasRowHeader: boolean;
@@ -508,7 +525,9 @@ export declare interface TemplateFieldAttributes {
508
525
  }
509
526
 
510
527
  export declare type TemplateFormPagination = Omit<FormPagination, "onNext" | "onPrev"> & {
528
+ readonly nextId?: string | undefined;
511
529
  readonly nextLabel: string;
530
+ readonly previousId?: string | undefined;
512
531
  readonly previousLabel: string;
513
532
  };
514
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,
@@ -59512,6 +59619,24 @@ function signatureName(path2) {
59512
59619
  function actionValue(kind, path2) {
59513
59620
  return `${kind}${pathParts(path2).map((part) => `[${part}]`).join("")}`;
59514
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
+ }
59515
59640
  function lastLinkId(path2) {
59516
59641
  return path2.at(-1)?.linkId ?? "";
59517
59642
  }
@@ -59536,7 +59661,7 @@ function isDefined(value) {
59536
59661
  function isPreservedOptionToken$1(token) {
59537
59662
  return token.includes("__custom__") || token.includes("__legacy__");
59538
59663
  }
59539
- function mediaHtml(attachment, fallbackLabel) {
59664
+ function mediaHtml(attachment, fallbackLabel, id2) {
59540
59665
  if (attachment === void 0) {
59541
59666
  return "";
59542
59667
  }
@@ -59550,12 +59675,12 @@ function mediaHtml(attachment, fallbackLabel) {
59550
59675
  return `<img${attribute("src", source)}${attribute("alt", label)}>`;
59551
59676
  }
59552
59677
  if (contentType?.startsWith("audio/")) {
59553
- return `<audio controls${attribute("src", source)}></audio>`;
59678
+ return `<audio controls${attribute("id", id2)}${attribute("src", source)}></audio>`;
59554
59679
  }
59555
59680
  if (contentType?.startsWith("video/")) {
59556
- return `<video controls${attribute("src", source)}></video>`;
59681
+ return `<video controls${attribute("id", id2)}${attribute("src", source)}></video>`;
59557
59682
  }
59558
- 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>`;
59559
59684
  }
59560
59685
  function formFieldsTemplate(properties) {
59561
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;
@@ -59571,7 +59696,7 @@ function formFieldsTemplate(properties) {
59571
59696
  properties.after ?? "",
59572
59697
  properties.signature ?? "",
59573
59698
  renderPagination(properties),
59574
- `<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>`
59575
59700
  ].join("");
59576
59701
  }
59577
59702
  function fieldAttributes$1(path2, field) {
@@ -59682,7 +59807,7 @@ function tableColumn(column, renderHtml) {
59682
59807
  widthStyle: column.width ? `width:${column.width}` : void 0
59683
59808
  };
59684
59809
  }
59685
- function tableRow(row, renderHtml, strings2) {
59810
+ function tableRow(row, renderHtml, strings2, token) {
59686
59811
  const path2 = row.path;
59687
59812
  return {
59688
59813
  token: row.token,
@@ -59694,6 +59819,7 @@ function tableRow(row, renderHtml, strings2) {
59694
59819
  actionName: ACTION_FIELD,
59695
59820
  linkId: path2 ? lastLinkId(path2) : void 0,
59696
59821
  removeAction: row.onRemove !== void 0 && row.canRemove === true && path2 ? actionValue("remove-group", path2) : void 0,
59822
+ removeId: pathControlId(token, path2, "remove-group"),
59697
59823
  removeLabel: strings2.group.removeSection,
59698
59824
  removeLabelHtml: escapeHtml$1(strings2.group.removeSection)
59699
59825
  };
@@ -59717,9 +59843,9 @@ function renderPagination(properties) {
59717
59843
  }
59718
59844
  return [
59719
59845
  "<nav>",
59720
- 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>`,
59721
59847
  `<span>${String(pagination.current)} / ${String(pagination.total)}</span>`,
59722
- 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>`,
59723
59849
  "</nav>"
59724
59850
  ].join("");
59725
59851
  }
@@ -61163,7 +61289,7 @@ function stripHtmlTag(ssr) {
61163
61289
  return ssr.replaceAll(/<x-html\b[^>]*>/gu, "").replaceAll("</x-html>", "");
61164
61290
  }
61165
61291
  function AnswerList(properties) {
61166
- const { templates } = useHtmxTheme();
61292
+ const { templates, token } = useHtmxTheme();
61167
61293
  const renderHtml = useHtml();
61168
61294
  const strings2 = useStrings();
61169
61295
  const children = Children.toArray(properties.children);
@@ -61173,6 +61299,7 @@ function AnswerList(properties) {
61173
61299
  children: renderHtml(properties.children),
61174
61300
  actionName: ACTION_FIELD,
61175
61301
  addAction: properties.onAdd !== void 0 && properties.canAdd && path2 ? actionValue("add-answer", path2) : void 0,
61302
+ addId: pathControlId(token, path2, "add-answer"),
61176
61303
  addLabel: strings2.selection.addAnother,
61177
61304
  count: path2 ? children.length : void 0,
61178
61305
  countName: path2 ? countName(path2) : void 0,
@@ -61181,7 +61308,7 @@ function AnswerList(properties) {
61181
61308
  });
61182
61309
  }
61183
61310
  function AnswerScaffold(properties) {
61184
- const { templates } = useHtmxTheme();
61311
+ const { templates, token } = useHtmxTheme();
61185
61312
  const renderHtml = useHtml();
61186
61313
  const strings2 = useStrings();
61187
61314
  const path2 = properties.path;
@@ -61193,6 +61320,7 @@ function AnswerScaffold(properties) {
61193
61320
  actionName: ACTION_FIELD,
61194
61321
  linkId: path2 ? lastLinkId(path2) : void 0,
61195
61322
  removeAction: properties.onRemove !== void 0 && properties.canRemove && path2 ? actionValue("remove-answer", path2) : void 0,
61323
+ removeId: pathControlId(token, path2, "remove-answer"),
61196
61324
  removeLabel: strings2.group.removeSection
61197
61325
  });
61198
61326
  }
@@ -61262,6 +61390,7 @@ function CheckboxList(properties) {
61262
61390
  orientation: properties.orientation,
61263
61391
  options: options.map((option) => ({
61264
61392
  ...option,
61393
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61265
61394
  selected: selectedTokens.has(option.token),
61266
61395
  disabled: Boolean(properties.disabled) || option.disabled,
61267
61396
  hiddenInput: name && preservedByToken.has(option.token) ? { name, value: option.token } : void 0
@@ -61281,15 +61410,19 @@ function CheckboxList(properties) {
61281
61410
  });
61282
61411
  }
61283
61412
  function CustomOptionForm(properties) {
61284
- const { templates } = useHtmxTheme();
61413
+ const { templates, token } = useHtmxTheme();
61285
61414
  const renderHtml = useHtml();
61286
61415
  const strings2 = useStrings();
61416
+ const id2 = properties.id ?? stableId(token, "custom-option-form");
61287
61417
  return renderTemplate(templates.CustomOptionForm, {
61418
+ id: id2,
61288
61419
  canSubmit: properties.canSubmit,
61289
61420
  actionName: ACTION_FIELD,
61290
61421
  cancelLabel: strings2.dialog.cancel,
61422
+ cancelId: stableId(id2, "cancel"),
61291
61423
  content: renderHtml(properties.content),
61292
61424
  errors: renderHtml(properties.errors),
61425
+ submitId: stableId(id2, "submit"),
61293
61426
  submitLabel: strings2.dialog.submit
61294
61427
  });
61295
61428
  }
@@ -61370,6 +61503,7 @@ function FileInput(properties) {
61370
61503
  disabled: properties.disabled,
61371
61504
  accept: properties.accept,
61372
61505
  clearLabel: strings2.file.clearAction,
61506
+ clearId: stableId(properties.id, "clear"),
61373
61507
  hiddenValue: properties.value && attributes.name ? JSON.stringify(properties.value) : void 0,
61374
61508
  clearAction: properties.value !== void 0 && attributes.name !== void 0 && !properties.disabled,
61375
61509
  dataLinkId: attributes["data-fb-link-id"],
@@ -61383,6 +61517,7 @@ function Flyover(properties) {
61383
61517
  const strings2 = useStrings();
61384
61518
  return renderTemplate(templates.Flyover, {
61385
61519
  id: properties.id,
61520
+ buttonId: stableId(properties.id, "button"),
61386
61521
  ariaLabel: strings2.aria.flyover,
61387
61522
  children: renderHtml(properties.children)
61388
61523
  });
@@ -61396,18 +61531,22 @@ function Footer(properties) {
61396
61531
  });
61397
61532
  }
61398
61533
  function Form(properties) {
61399
- const { action: action2, hiddenFields, templates } = useHtmxTheme();
61534
+ const { action: action2, hiddenFields, templates, token } = useHtmxTheme();
61400
61535
  const renderHtml = useHtml();
61401
61536
  const strings2 = useStrings();
61537
+ const id2 = properties.id ?? token;
61402
61538
  const pagination = properties.pagination ? {
61403
61539
  current: properties.pagination.current,
61404
61540
  total: properties.pagination.total,
61405
61541
  disabledPrev: properties.pagination.disabledPrev,
61406
61542
  disabledNext: properties.pagination.disabledNext,
61543
+ nextId: stableId(id2, "pagination", "next"),
61407
61544
  nextLabel: strings2.pagination.next,
61545
+ previousId: stableId(id2, "pagination", "previous"),
61408
61546
  previousLabel: strings2.pagination.previous
61409
61547
  } : void 0;
61410
61548
  const formProperties = {
61549
+ id: id2,
61411
61550
  title: properties.title,
61412
61551
  description: properties.description,
61413
61552
  customExtensions: properties.customExtensions,
@@ -61423,12 +61562,12 @@ function Form(properties) {
61423
61562
  const fields = [hiddenFields, formFieldsTemplate(formProperties)].join("");
61424
61563
  return renderTemplate(templates.Form, {
61425
61564
  ...formProperties,
61426
- attributes: defaultAttributes(action2),
61565
+ attributes: { id: id2, ...defaultAttributes(action2) },
61427
61566
  fields
61428
61567
  });
61429
61568
  }
61430
61569
  function GroupList(properties) {
61431
- const { templates } = useHtmxTheme();
61570
+ const { templates, token } = useHtmxTheme();
61432
61571
  const renderHtml = useHtml();
61433
61572
  const strings2 = useStrings();
61434
61573
  const groups2 = Children.toArray(properties.children);
@@ -61443,6 +61582,7 @@ function GroupList(properties) {
61443
61582
  children: renderHtml(properties.children),
61444
61583
  actionName: ACTION_FIELD,
61445
61584
  addAction: properties.onAdd !== void 0 && properties.canAdd && path2 ? actionValue("add-group", path2) : void 0,
61585
+ addId: pathControlId(token, path2, "add-group"),
61446
61586
  addLabel: strings2.group.addSection,
61447
61587
  count: path2 ? count : void 0,
61448
61588
  countName: path2 ? countName(path2) : void 0,
@@ -61450,7 +61590,7 @@ function GroupList(properties) {
61450
61590
  });
61451
61591
  }
61452
61592
  function GroupScaffold(properties) {
61453
- const { templates } = useHtmxTheme();
61593
+ const { templates, token } = useHtmxTheme();
61454
61594
  const renderHtml = useHtml();
61455
61595
  const strings2 = useStrings();
61456
61596
  const path2 = properties.path;
@@ -61473,8 +61613,10 @@ function GroupScaffold(properties) {
61473
61613
  expandedValue: expandedName$1 ? String(Boolean(properties.isExpanded)) : void 0,
61474
61614
  expandLabel: strings2.collapsible.expand,
61475
61615
  removeAction: properties.onRemove !== void 0 && properties.canRemove && path2 ? actionValue("remove-group", path2) : void 0,
61616
+ removeId: pathControlId(token, path2, "remove-group"),
61476
61617
  removeLabel: strings2.group.removeSection,
61477
61618
  toggleAction,
61619
+ toggleId: pathControlId(token, path2, "toggle-expanded"),
61478
61620
  summaryLabel: properties.isExpanded ? strings2.collapsible.collapse : strings2.collapsible.expand,
61479
61621
  expandedChildren: properties.isExpanded ? renderHtml(properties.children) : ""
61480
61622
  });
@@ -61493,6 +61635,7 @@ function Help(properties) {
61493
61635
  const strings2 = useStrings();
61494
61636
  return renderTemplate(templates.Help, {
61495
61637
  id: properties.id,
61638
+ buttonId: stableId(properties.id, "button"),
61496
61639
  ariaLabel: strings2.aria.help,
61497
61640
  children: renderHtml(properties.children)
61498
61641
  });
@@ -61545,18 +61688,26 @@ function Label(properties) {
61545
61688
  help: renderHtml(properties.help),
61546
61689
  legal: renderHtml(properties.legal),
61547
61690
  flyover: renderHtml(properties.flyover),
61548
- media: mediaHtml(properties.media, strings2.inputs.attachmentLabel),
61691
+ media: mediaHtml(
61692
+ properties.media,
61693
+ strings2.inputs.attachmentLabel,
61694
+ stableId(properties.id, "media")
61695
+ ),
61549
61696
  ...properties.supportHyperlinks ? {
61550
- supportHyperlinks: properties.supportHyperlinks.map((link) => ({
61551
- ...link,
61552
- labelHtml: escapeHtml$1(link.label || link.href)
61553
- }))
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
+ )
61554
61704
  } : {}
61555
61705
  });
61556
61706
  }
61557
61707
  function LanguageSelector(properties) {
61558
- const { templates } = useHtmxTheme();
61708
+ const { templates, token } = useHtmxTheme();
61559
61709
  return renderTemplate(templates.LanguageSelector, {
61710
+ id: properties.id ?? stableId(token, "language"),
61560
61711
  value: properties.value,
61561
61712
  name: LANGUAGE_FIELD,
61562
61713
  options: properties.options.map((option) => ({
@@ -61571,6 +61722,7 @@ function Legal(properties) {
61571
61722
  const strings2 = useStrings();
61572
61723
  return renderTemplate(templates.Legal, {
61573
61724
  id: properties.id,
61725
+ buttonId: stableId(properties.id, "button"),
61574
61726
  ariaLabel: strings2.aria.legal,
61575
61727
  children: renderHtml(properties.children)
61576
61728
  });
@@ -61579,6 +61731,7 @@ function Link(properties) {
61579
61731
  const { templates } = useHtmxTheme();
61580
61732
  const renderHtml = useHtml();
61581
61733
  return renderTemplate(templates.Link, {
61734
+ id: properties.id,
61582
61735
  href: properties.href,
61583
61736
  target: properties.target,
61584
61737
  rel: properties.rel,
@@ -61637,6 +61790,7 @@ function MultiSelectInput(properties) {
61637
61790
  baselineName,
61638
61791
  options: options.map((option) => ({
61639
61792
  ...option,
61793
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61640
61794
  selected: selectedTokens.has(option.token),
61641
61795
  disabled: Boolean(properties.disabled) || option.disabled,
61642
61796
  hiddenInput: name && preservedByToken.has(option.token) ? { name, value: option.token } : void 0
@@ -61682,7 +61836,7 @@ function OptionsLoading(properties) {
61682
61836
  });
61683
61837
  }
61684
61838
  function QuestionScaffold(properties) {
61685
- const { templates } = useHtmxTheme();
61839
+ const { templates, token } = useHtmxTheme();
61686
61840
  const renderHtml = useHtml();
61687
61841
  const strings2 = useStrings();
61688
61842
  const path2 = properties.path;
@@ -61704,6 +61858,7 @@ function QuestionScaffold(properties) {
61704
61858
  expandedValue: expandedName$1 ? String(Boolean(properties.isExpanded)) : void 0,
61705
61859
  expandLabel: strings2.collapsible.expand,
61706
61860
  toggleAction,
61861
+ toggleId: pathControlId(token, path2, "toggle-expanded"),
61707
61862
  summaryLabel: properties.isExpanded ? strings2.collapsible.collapse : strings2.collapsible.expand,
61708
61863
  expandedChildren: properties.isExpanded ? renderHtml(properties.children) : ""
61709
61864
  });
@@ -61757,6 +61912,7 @@ function RadioButtonList(properties) {
61757
61912
  hiddenValue: (preserveValue || mirrorValue) && selectedToken && attributes.name ? selectedToken : void 0,
61758
61913
  options: options.map((option) => ({
61759
61914
  ...option,
61915
+ id: stableId(properties.id, "option", option.token) ?? option.token,
61760
61916
  checked: option.token === selectedToken,
61761
61917
  disabled: Boolean(properties.disabled) || Boolean(option.disabled)
61762
61918
  })),
@@ -61878,10 +62034,12 @@ function TabContainer(properties) {
61878
62034
  });
61879
62035
  }
61880
62036
  function Table(properties) {
61881
- const { templates } = useHtmxTheme();
62037
+ const { templates, token } = useHtmxTheme();
61882
62038
  const renderHtml = useHtml();
61883
62039
  const strings2 = useStrings();
61884
- const rows = properties.rows.map((row) => tableRow(row, renderHtml, strings2));
62040
+ const rows = properties.rows.map(
62041
+ (row) => tableRow(row, renderHtml, strings2, token)
62042
+ );
61885
62043
  return renderTemplate(templates.Table, {
61886
62044
  columns: properties.columns.map(
61887
62045
  (column) => tableColumn(column, renderHtml)
@@ -61993,6 +62151,7 @@ function renderStoreFields(store, templates, action2) {
61993
62151
  const theme2 = createTheme();
61994
62152
  const htmxTheme = {
61995
62153
  templates,
62154
+ token: store.token,
61996
62155
  hiddenFields: renderHiddenFieldsForStore(store),
61997
62156
  activeTabValue: activeTab,
61998
62157
  action: action2
@@ -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.3",
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/theme": "0.4.0",
36
- "@formbox/strings": "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",