@fieldia/widgets 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +2 -1
  2. package/package.json +2 -2
  3. package/src/index.d.mts +2 -0
  4. package/src/index.d.ts +2 -0
  5. package/src/index.js +6 -1
  6. package/src/index.js.map +1 -1
  7. package/src/index.mjs +2 -0
  8. package/src/index.mjs.map +1 -1
  9. package/src/lib/badge.d.mts +8 -0
  10. package/src/lib/badge.d.ts +8 -0
  11. package/src/lib/badge.js +37 -0
  12. package/src/lib/badge.js.map +1 -0
  13. package/src/lib/badge.mjs +33 -0
  14. package/src/lib/badge.mjs.map +1 -0
  15. package/src/lib/calendar.d.mts +3 -1
  16. package/src/lib/calendar.d.ts +3 -1
  17. package/src/lib/calendar.js +17 -1
  18. package/src/lib/calendar.js.map +1 -1
  19. package/src/lib/calendar.mjs +17 -1
  20. package/src/lib/calendar.mjs.map +1 -1
  21. package/src/lib/cards.js +12 -4
  22. package/src/lib/cards.js.map +1 -1
  23. package/src/lib/cards.mjs +13 -5
  24. package/src/lib/cards.mjs.map +1 -1
  25. package/src/lib/choice-images.js +1 -1
  26. package/src/lib/choice-images.js.map +1 -1
  27. package/src/lib/choice-images.mjs +1 -1
  28. package/src/lib/choice-images.mjs.map +1 -1
  29. package/src/lib/choice-tags.js +2 -2
  30. package/src/lib/choice-tags.js.map +1 -1
  31. package/src/lib/choice-tags.mjs +2 -2
  32. package/src/lib/choice-tags.mjs.map +1 -1
  33. package/src/lib/colour.d.mts +11 -0
  34. package/src/lib/colour.d.ts +11 -0
  35. package/src/lib/colour.js +114 -0
  36. package/src/lib/colour.js.map +1 -0
  37. package/src/lib/colour.mjs +110 -0
  38. package/src/lib/colour.mjs.map +1 -0
  39. package/src/lib/copy.d.mts +8 -0
  40. package/src/lib/copy.d.ts +8 -0
  41. package/src/lib/copy.js +58 -0
  42. package/src/lib/copy.js.map +1 -0
  43. package/src/lib/copy.mjs +54 -0
  44. package/src/lib/copy.mjs.map +1 -0
  45. package/src/lib/date-range.d.mts +2 -0
  46. package/src/lib/date-range.d.ts +2 -0
  47. package/src/lib/date-range.js +220 -0
  48. package/src/lib/date-range.js.map +1 -0
  49. package/src/lib/date-range.mjs +216 -0
  50. package/src/lib/date-range.mjs.map +1 -0
  51. package/src/lib/display.d.mts +25 -1
  52. package/src/lib/display.d.ts +25 -1
  53. package/src/lib/display.js +42 -2
  54. package/src/lib/display.js.map +1 -1
  55. package/src/lib/display.mjs +39 -2
  56. package/src/lib/display.mjs.map +1 -1
  57. package/src/lib/distribution.d.mts +13 -0
  58. package/src/lib/distribution.d.ts +13 -0
  59. package/src/lib/distribution.js +159 -0
  60. package/src/lib/distribution.js.map +1 -0
  61. package/src/lib/distribution.mjs +152 -0
  62. package/src/lib/distribution.mjs.map +1 -0
  63. package/src/lib/duration.d.mts +18 -0
  64. package/src/lib/duration.d.ts +18 -0
  65. package/src/lib/duration.js +137 -0
  66. package/src/lib/duration.js.map +1 -0
  67. package/src/lib/duration.mjs +128 -0
  68. package/src/lib/duration.mjs.map +1 -0
  69. package/src/lib/embed.d.mts +3 -0
  70. package/src/lib/embed.d.ts +3 -0
  71. package/src/lib/embed.js +97 -0
  72. package/src/lib/embed.js.map +1 -0
  73. package/src/lib/embed.mjs +92 -0
  74. package/src/lib/embed.mjs.map +1 -0
  75. package/src/lib/extras.js +6 -0
  76. package/src/lib/extras.js.map +1 -1
  77. package/src/lib/extras.mjs +6 -0
  78. package/src/lib/extras.mjs.map +1 -1
  79. package/src/lib/files.d.mts +11 -0
  80. package/src/lib/files.d.ts +11 -0
  81. package/src/lib/files.js +86 -9
  82. package/src/lib/files.js.map +1 -1
  83. package/src/lib/files.mjs +85 -9
  84. package/src/lib/files.mjs.map +1 -1
  85. package/src/lib/kind-parts.d.mts +4 -3
  86. package/src/lib/kind-parts.d.ts +4 -3
  87. package/src/lib/kind-parts.js +5 -3
  88. package/src/lib/kind-parts.js.map +1 -1
  89. package/src/lib/kind-parts.mjs +5 -3
  90. package/src/lib/kind-parts.mjs.map +1 -1
  91. package/src/lib/labels.d.mts +42 -0
  92. package/src/lib/labels.d.ts +42 -0
  93. package/src/lib/labels.js +32 -0
  94. package/src/lib/labels.js.map +1 -1
  95. package/src/lib/labels.mjs +32 -0
  96. package/src/lib/labels.mjs.map +1 -1
  97. package/src/lib/layout-styles.d.mts +1 -1
  98. package/src/lib/layout-styles.d.ts +1 -1
  99. package/src/lib/layout-styles.js +2 -0
  100. package/src/lib/layout-styles.js.map +1 -1
  101. package/src/lib/layout-styles.mjs +2 -0
  102. package/src/lib/layout-styles.mjs.map +1 -1
  103. package/src/lib/lines.d.mts +12 -2
  104. package/src/lib/lines.d.ts +12 -2
  105. package/src/lib/lines.js +251 -15
  106. package/src/lib/lines.js.map +1 -1
  107. package/src/lib/lines.mjs +250 -16
  108. package/src/lib/lines.mjs.map +1 -1
  109. package/src/lib/links-table.d.mts +11 -0
  110. package/src/lib/links-table.d.ts +11 -0
  111. package/src/lib/links-table.js +116 -0
  112. package/src/lib/links-table.js.map +1 -0
  113. package/src/lib/links-table.mjs +112 -0
  114. package/src/lib/links-table.mjs.map +1 -0
  115. package/src/lib/locales/ar.js +32 -0
  116. package/src/lib/locales/ar.js.map +1 -1
  117. package/src/lib/locales/ar.mjs +32 -0
  118. package/src/lib/locales/ar.mjs.map +1 -1
  119. package/src/lib/locales/de.js +32 -0
  120. package/src/lib/locales/de.js.map +1 -1
  121. package/src/lib/locales/de.mjs +32 -0
  122. package/src/lib/locales/de.mjs.map +1 -1
  123. package/src/lib/locales/fr.js +32 -0
  124. package/src/lib/locales/fr.js.map +1 -1
  125. package/src/lib/locales/fr.mjs +32 -0
  126. package/src/lib/locales/fr.mjs.map +1 -1
  127. package/src/lib/payments.d.mts +2 -0
  128. package/src/lib/payments.d.ts +2 -0
  129. package/src/lib/payments.js +94 -0
  130. package/src/lib/payments.js.map +1 -0
  131. package/src/lib/payments.mjs +90 -0
  132. package/src/lib/payments.mjs.map +1 -0
  133. package/src/lib/popup.d.mts +11 -0
  134. package/src/lib/popup.d.ts +11 -0
  135. package/src/lib/popup.js +48 -0
  136. package/src/lib/popup.js.map +1 -0
  137. package/src/lib/popup.mjs +45 -0
  138. package/src/lib/popup.mjs.map +1 -0
  139. package/src/lib/priority.d.mts +10 -0
  140. package/src/lib/priority.d.ts +10 -0
  141. package/src/lib/priority.js +59 -0
  142. package/src/lib/priority.js.map +1 -0
  143. package/src/lib/priority.mjs +55 -0
  144. package/src/lib/priority.mjs.map +1 -0
  145. package/src/lib/progress.js +3 -0
  146. package/src/lib/progress.js.map +1 -1
  147. package/src/lib/progress.mjs +3 -0
  148. package/src/lib/progress.mjs.map +1 -1
  149. package/src/lib/properties.js +100 -9
  150. package/src/lib/properties.js.map +1 -1
  151. package/src/lib/properties.mjs +100 -9
  152. package/src/lib/properties.mjs.map +1 -1
  153. package/src/lib/read-text.d.mts +25 -0
  154. package/src/lib/read-text.d.ts +25 -0
  155. package/src/lib/read-text.js +102 -0
  156. package/src/lib/read-text.js.map +1 -0
  157. package/src/lib/read-text.mjs +98 -0
  158. package/src/lib/read-text.mjs.map +1 -0
  159. package/src/lib/relations.d.mts +12 -0
  160. package/src/lib/relations.d.ts +12 -0
  161. package/src/lib/relations.js +99 -8
  162. package/src/lib/relations.js.map +1 -1
  163. package/src/lib/relations.mjs +97 -8
  164. package/src/lib/relations.mjs.map +1 -1
  165. package/src/lib/slider.js +1 -1
  166. package/src/lib/slider.js.map +1 -1
  167. package/src/lib/slider.mjs +1 -1
  168. package/src/lib/slider.mjs.map +1 -1
  169. package/src/lib/state-dot.d.mts +2 -0
  170. package/src/lib/state-dot.d.ts +2 -0
  171. package/src/lib/state-dot.js +107 -0
  172. package/src/lib/state-dot.js.map +1 -0
  173. package/src/lib/state-dot.mjs +103 -0
  174. package/src/lib/state-dot.mjs.map +1 -0
  175. package/src/lib/statusbar.js +151 -14
  176. package/src/lib/statusbar.js.map +1 -1
  177. package/src/lib/statusbar.mjs +151 -14
  178. package/src/lib/statusbar.mjs.map +1 -1
  179. package/src/lib/styles-business.d.mts +8 -0
  180. package/src/lib/styles-business.d.ts +8 -0
  181. package/src/lib/styles-business.js +120 -0
  182. package/src/lib/styles-business.js.map +1 -0
  183. package/src/lib/styles-business.mjs +117 -0
  184. package/src/lib/styles-business.mjs.map +1 -0
  185. package/src/lib/styles-kinds.d.mts +1 -1
  186. package/src/lib/styles-kinds.d.ts +1 -1
  187. package/src/lib/styles-kinds.js +1 -1
  188. package/src/lib/styles-kinds.mjs +1 -1
  189. package/src/lib/styles-parts.d.mts +16 -0
  190. package/src/lib/styles-parts.d.ts +16 -0
  191. package/src/lib/styles-parts.js +69 -0
  192. package/src/lib/styles-parts.js.map +1 -0
  193. package/src/lib/styles-parts.mjs +66 -0
  194. package/src/lib/styles-parts.mjs.map +1 -0
  195. package/src/lib/styles-sheet.d.mts +6 -0
  196. package/src/lib/styles-sheet.d.ts +6 -0
  197. package/src/lib/styles-sheet.js +123 -0
  198. package/src/lib/styles-sheet.js.map +1 -0
  199. package/src/lib/styles-sheet.mjs +120 -0
  200. package/src/lib/styles-sheet.mjs.map +1 -0
  201. package/src/lib/styles.d.mts +10 -0
  202. package/src/lib/styles.d.ts +10 -0
  203. package/src/lib/styles.js +234 -19
  204. package/src/lib/styles.js.map +1 -1
  205. package/src/lib/styles.mjs +233 -18
  206. package/src/lib/styles.mjs.map +1 -1
  207. package/src/lib/tax-totals.d.mts +2 -0
  208. package/src/lib/tax-totals.d.ts +2 -0
  209. package/src/lib/tax-totals.js +92 -0
  210. package/src/lib/tax-totals.js.map +1 -0
  211. package/src/lib/tax-totals.mjs +88 -0
  212. package/src/lib/tax-totals.mjs.map +1 -0
  213. package/src/lib/timer.d.mts +4 -0
  214. package/src/lib/timer.d.ts +4 -0
  215. package/src/lib/timer.js +62 -0
  216. package/src/lib/timer.js.map +1 -0
  217. package/src/lib/timer.mjs +57 -0
  218. package/src/lib/timer.mjs.map +1 -0
  219. package/src/lib/widgets.d.mts +18 -3
  220. package/src/lib/widgets.d.ts +18 -3
  221. package/src/lib/widgets.js +63 -5
  222. package/src/lib/widgets.js.map +1 -1
  223. package/src/lib/widgets.mjs +63 -5
  224. package/src/lib/widgets.mjs.map +1 -1
  225. package/src/lib/yes-no.js +1 -1
  226. package/src/lib/yes-no.js.map +1 -1
  227. package/src/lib/yes-no.mjs +1 -1
  228. package/src/lib/yes-no.mjs.map +1 -1
@@ -1,6 +1,9 @@
1
1
  import { KINDS_CSS } from './styles-kinds.mjs';
2
2
  import { CHOICES_CSS } from './styles-choices.mjs';
3
3
  import { INPUTS_CSS } from './styles-inputs.mjs';
4
+ import { PARTS_CSS } from './styles-parts.mjs';
5
+ import { SHEET_CSS } from './styles-sheet.mjs';
6
+ import { BUSINESS_CSS } from './styles-business.mjs';
4
7
  /**
5
8
  * Fieldia's stylesheet: two skins and the layout chrome, all scoped to the
6
9
  * form's own root (`.fd-form[data-fd-skin=…]`), so two forms on one page can
@@ -15,6 +18,13 @@ import { INPUTS_CSS } from './styles-inputs.mjs';
15
18
  * form mirrors without a second stylesheet.
16
19
  */
17
20
  import { LAYOUT_CSS } from './layout-styles.mjs';
21
+ /**
22
+ * The widths of a form at which its groups take their medium and their narrow
23
+ * columns: up to 760px and up to 520px of the form's own width, as the
24
+ * stylesheet's @container rules say (a spec holds the two together). What
25
+ * shows a form at a width — the designer's canvas — asks these.
26
+ */
27
+ export const FORM_WIDTHS = { medium: 760, narrow: 520 };
18
28
  /** The skins and the parts' chrome. One literal, so the script bundle can minify it as CSS. */
19
29
  const SKINS_CSS = /* css */ `
20
30
  /* .fd-theme carries the same tokens for things outside a form, such as a dialog over the page. One inside a form
@@ -111,6 +121,9 @@ const SKINS_CSS = /* css */ `
111
121
  align-items: start;
112
122
  }
113
123
  .fd-grid > * { grid-column: span min(var(--fd-span, 1), var(--fd-cols)); }
124
+ /* Twelfths keep the page's gap while it takes under half their width, so they line up with the columns
125
+ round them; in a narrow column it shrinks with them instead of pushing past its edge. */
126
+ .fd-grid[data-twelfths] { column-gap: min(var(--fd-gap-x), 100% / 24); }
114
127
  .fd-field { display: grid; gap: 4px; min-width: 0; }
115
128
  .fd-label { font-weight: var(--fd-label-weight); color: var(--fd-text); }
116
129
  /* The ✓ of a field filled in right, by its label. */
@@ -119,6 +132,12 @@ const SKINS_CSS = /* css */ `
119
132
  .fd-form[data-fd-skin="outlined"] .fd-field.fd-required > .fd-label::after { content: ""; }
120
133
  .fd-form[data-fd-skin="outlined"] .fd-field.fd-required > .fd-label::before { content: "* "; color: var(--fd-error); }
121
134
  .fd-help { color: var(--fd-muted); font-size: 12.5px; }
135
+ /* A field's help behind a (?) by its label: a bubble on hover, focus or a tap. */
136
+ .fd-help-tip-wrap { position: relative; display: inline-flex; vertical-align: 0.05em; margin-inline-start: 6px; }
137
+ .fd-help-tip { all: unset; box-sizing: border-box; display: inline-grid; place-items: center; width: 16px; height: 16px; border: 1px solid var(--fd-border); border-radius: 50%; color: var(--fd-muted); font-size: 11px; font-weight: 600; line-height: 1; cursor: help; }
138
+ .fd-help-tip:hover, .fd-help-tip[aria-expanded="true"] { color: var(--fd-accent); border-color: var(--fd-accent); }
139
+ .fd-help-tip:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
140
+ .fd-help-bubble { position: absolute; inset-block-start: calc(100% + 6px); inset-inline-start: -8px; z-index: 30; width: max-content; max-width: min(280px, 70vw); padding: 6px 9px; border-radius: 6px; background: var(--fd-text); color: var(--fd-surface); font-size: 12.5px; font-weight: 400; line-height: 1.4; white-space: normal; box-shadow: 0 6px 18px rgb(0 0 0 / 0.18); }
122
141
  /*
123
142
  * A page's words run the way they are written — a sentence left in English on
124
143
  * an Arabic page keeps its full stop at its end — and line up with the form.
@@ -146,6 +165,18 @@ const SKINS_CSS = /* css */ `
146
165
  .fd-form[data-fd-skin="underline"] .fd-field[data-type="one2many"] { grid-template-columns: minmax(0, 1fr); }
147
166
  .fd-form[data-fd-skin="underline"] .fd-field[data-type="one2many"] > * { grid-column: 1 !important; }
148
167
  .fd-form[data-fd-skin="underline"] .fd-field:where(:not([data-labels])) > :not(.fd-label) { grid-column: 2; }
168
+ /* A row of twelfths led by a field with its label beside — the skin's (data-row="skin") or the page's
169
+ ("beside") — Flectra's o_row: that label sits with the labels round it, the parts share the values' room. */
170
+ .fd-form[data-fd-skin="underline"] .fd-section[data-row="skin"] { --fd-row-label: var(--fd-label-width, 11em); --fd-row-gap: 12px; --fd-row-place: absolute; --fd-row-drop: max(0px, calc((var(--fd-control-height, 30px) - 1.45em) / 2)); }
171
+ .fd-form .fd-section[data-row="beside"] { --fd-row-label: var(--fd-label-width, 140px); --fd-row-gap: 14px; --fd-row-place: absolute; --fd-row-drop: max(0px, calc((var(--fd-control-height, 30px) - 1.45em) / 2)); }
172
+ .fd-form .fd-section[data-row] { padding-inline-start: calc(var(--fd-row-label, 0px) + var(--fd-row-gap, 0px)); }
173
+ .fd-form .fd-section[data-row] > .fd-grid > .fd-field:first-child { grid-template-columns: minmax(0, 1fr); position: relative; }
174
+ .fd-form .fd-section[data-row] > .fd-grid > .fd-field:first-child > :not(.fd-label) { grid-column: 1; }
175
+ .fd-form .fd-section[data-row] > .fd-grid > .fd-field:first-child > .fd-label {
176
+ /* Out of the field's own box, back over the row's room for it — where the row has one. */
177
+ position: var(--fd-row-place, static); inset-inline-start: calc(-1 * (var(--fd-row-label, 0px) + var(--fd-row-gap, 0px))); inset-block-start: 0;
178
+ width: var(--fd-row-label, auto); padding-block-start: var(--fd-row-drop, 0px);
179
+ }
149
180
  /* A label beside every value needs room twice over: the underline skin stacks its columns sooner. */
150
181
  @container (max-width: 760px) {
151
182
  .fd-form[data-fd-skin="underline"] .fd-grid { --fd-cols: 1; }
@@ -157,6 +188,9 @@ const SKINS_CSS = /* css */ `
157
188
  }
158
189
  @container (max-width: 520px) {
159
190
  .fd-grid { --fd-cols: 1; }
191
+ /* Labels go above here: a row's first one too, the parts stacked under it. */
192
+ .fd-form .fd-section.fd-section[data-row] { --fd-row-label: auto; --fd-row-gap: 0px; --fd-row-place: static; --fd-row-drop: 0px; }
193
+ .fd-form .fd-section[data-row] { padding-inline-start: 0; }
160
194
  .fd-form[data-fd-skin="underline"] .fd-field:where(:not([data-labels])) { grid-template-columns: minmax(0, 1fr); }
161
195
  .fd-form[data-fd-skin="underline"] .fd-field:where(:not([data-labels])) > * { grid-column: 1 !important; }
162
196
  /* The narrow count, or one column when only the medium one was given. */
@@ -191,6 +225,9 @@ const SKINS_CSS = /* css */ `
191
225
  /* A whole form locked reads as a record: plain values in either skin, no boxes, and no prompts to type. */
192
226
  .fd-form[data-readonly] .fd-input[readonly] { background: transparent; border-color: transparent; padding-inline: 0; color: var(--fd-text); }
193
227
  .fd-form[data-readonly] .fd-input::placeholder { color: transparent; }
228
+ /* Nothing to read in a read-only box draws nothing, in either skin: no prompt to type, and no date's dd.mm.yyyy or its calendar. */
229
+ .fd-input[readonly]::placeholder, .fd-form .fd-input.fd-blank[readonly] { color: transparent; }
230
+ .fd-blank[readonly]::-webkit-calendar-picker-indicator { display: none; }
194
231
  .fd-form[data-readonly] select.fd-input:disabled { appearance: none; background: transparent; border-color: transparent; padding-inline: 0; color: var(--fd-text); opacity: 1; }
195
232
  .fd-input[aria-invalid="true"] { border-color: var(--fd-error); }
196
233
  .fd-form[data-fd-skin="outlined"] .fd-input[aria-invalid="true"]:focus { box-shadow: 0 0 0 2px rgba(255, 38, 5, 0.06); }
@@ -301,10 +338,66 @@ button.fd-chip-label:hover { text-decoration: underline; }
301
338
  .fd-lines-table td > .fd-checkbox, .fd-lines-table td > .fd-switch { margin-block-start: 6px; }
302
339
  .fd-lines-table .fd-lines-grip { width: 1px; min-width: 0; padding-inline: 6px 2px; }
303
340
  .fd-line-grip { display: block; margin-block-start: 7px; cursor: grab; }
304
- .fd-line-delete { border: none; background: none; cursor: pointer; color: var(--fd-muted); font-size: 16px; line-height: 1; padding: 4px 6px; border-radius: 4px; }
341
+ .fd-line-delete { border: none; background: none; cursor: pointer; color: var(--fd-muted); font-size: 16px; line-height: 1; padding: 4px 6px; border-radius: 4px; min-width: 24px; min-height: 24px; }
305
342
  .fd-line-delete:hover { color: var(--fd-error); background: var(--fd-error-soft); }
343
+ /* A table's own rules: a line's or a cell's tone and bold (Flectra's decoration-*), and buttons on a line. */
344
+ .fd-lines-table [data-tone="info"] { --fd-line-tone: var(--fd-info); --fd-tone: var(--fd-info); --fd-tone-soft: var(--fd-info-soft); }
345
+ .fd-lines-table [data-tone="success"] { --fd-line-tone: var(--fd-success); --fd-tone: var(--fd-success); --fd-tone-soft: var(--fd-success-soft); }
346
+ .fd-lines-table [data-tone="warning"] { --fd-line-tone: var(--fd-warning); --fd-tone: var(--fd-warning); --fd-tone-soft: var(--fd-warning-soft); }
347
+ .fd-lines-table [data-tone="danger"] { --fd-line-tone: var(--fd-error); --fd-tone: var(--fd-error); --fd-tone-soft: var(--fd-error-soft); }
348
+ .fd-lines-table [data-tone="muted"] { --fd-line-tone: var(--fd-muted); --fd-tone: var(--fd-muted); --fd-tone-soft: color-mix(in srgb, var(--fd-muted) 14%, transparent); }
349
+ .fd-lines-table [data-tone] :is(input, select, textarea, .fd-input, .fd-display) { color: var(--fd-line-tone); -webkit-text-fill-color: var(--fd-line-tone); }
350
+ .fd-lines-table :is(.fd-line-bold, .fd-cell-bold) :is(input, select, textarea, .fd-input, .fd-display) { font-weight: 600; }
351
+ .fd-lines-table td.fd-lines-tools { white-space: nowrap; }
352
+ /* With buttons on some lines, every line's × keeps to the end, so they line up. */
353
+ .fd-lines-table:has(.fd-line-button) td.fd-lines-tools { text-align: end; padding-inline-end: 4px; }
354
+ .fd-line-button { padding: 2px 6px; font-size: 12.5px; vertical-align: middle; }
355
+ .fd-line-button-icon { padding: 4px; color: var(--fd-muted); }
356
+ .fd-line-button-icon:hover { color: var(--fd-accent); }
357
+ .fd-line-button svg { width: 15px; height: 15px; display: block; }
306
358
  .fd-lines-totals td { padding: 8px; font-weight: 600; border-block-start: 1px solid var(--fd-border); font-variant-numeric: tabular-nums; }
307
359
  .fd-lines-adds { display: flex; flex-wrap: wrap; gap: 4px 16px; justify-self: start; }
360
+ .fd-lines-control { display: inline-flex; align-items: center; gap: 6px; }
361
+ .fd-lines-control svg, .fd-lines-chosen-button svg { width: 15px; height: 15px; }
362
+ /* Lines chosen for the table's buttons: a tick before each line's grip, and a bar of buttons over the table. */
363
+ .fd-line-lead { display: inline-flex; align-items: center; gap: 4px; margin-block-start: 6px; }
364
+ .fd-line-lead .fd-line-grip { margin-block-start: 0; }
365
+ .fd-lines-table th.fd-lines-grip:has(.fd-line-pick-all) { padding-block: 4px; }
366
+ .fd-lines-chosen { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding: 6px 10px; border-radius: var(--fd-radius); background: var(--fd-accent-soft); }
367
+ .fd-lines-chosen[hidden] { display: none; }
368
+ .fd-lines-chosen-count { font-weight: 600; font-size: 13px; margin-inline-end: 6px; }
369
+ .fd-lines-chosen-button { display: inline-flex; align-items: center; gap: 6px; padding-block: 3px; }
370
+ /* A line opened in a dialog: its own record's page, or its every field. */
371
+ .fd-lines-table .fd-line-open { border: none; background: none; cursor: pointer; color: var(--fd-accent); font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 4px; }
372
+ .fd-lines-table .fd-line-open:hover { background: var(--fd-accent-soft); }
373
+ .fd-links-table td { vertical-align: middle; }
374
+ .fd-links-table .fd-value-text { padding-block: 4px; }
375
+ /* Columns as wide as they hold (fit: content), the widest taking what is left. */
376
+ .fd-lines[data-fit="content"] .fd-lines-table { width: auto; max-width: 100%; }
377
+ .fd-lines[data-fit="content"] .fd-lines-table td { min-width: 0; }
378
+ /* Lines as cards (cards: narrow on a phone, or always): each a box, every cell its column's label above its value. */
379
+ .fd-lines[data-cards] { container: fd-lines / inline-size; }
380
+ @container fd-lines (max-width: 520px) {
381
+ .fd-lines[data-cards="narrow"] .fd-lines-table, .fd-lines[data-cards="narrow"] .fd-lines-table :is(tbody, tfoot) { display: block; }
382
+ .fd-lines[data-cards="narrow"] .fd-lines-table thead { display: none; }
383
+ .fd-lines[data-cards="narrow"] .fd-lines-table tr { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; position: relative; padding: 10px 12px; margin-block-end: 8px; border: 1px solid var(--fd-border); border-radius: var(--fd-radius); background: var(--fd-surface); }
384
+ .fd-lines[data-cards="narrow"] .fd-lines-table td { display: block; border: none; padding: 0; min-width: 0; }
385
+ .fd-lines[data-cards="narrow"] .fd-lines-table td[data-label]:not([colspan]:not([colspan="1"]))::before { content: attr(data-label); display: block; font-size: 12px; font-weight: 600; color: var(--fd-muted); }
386
+ .fd-lines[data-cards="narrow"] .fd-lines-table td.fd-lines-grip { position: absolute; inset-block-start: 6px; inset-inline-start: 6px; padding: 0; }
387
+ .fd-lines[data-cards="narrow"] .fd-lines-table td.fd-lines-grip .fd-line-grip { display: none; }
388
+ .fd-lines[data-cards="narrow"] .fd-lines-table tr:has(.fd-line-pick) { padding-inline-start: 36px; }
389
+ .fd-lines[data-cards="narrow"] .fd-lines-table td.fd-lines-tools { width: auto; text-align: end; }
390
+ .fd-lines[data-cards="narrow"] .fd-lines-totals td:empty { display: none; }
391
+ }
392
+ .fd-lines[data-cards="always"] .fd-lines-table, .fd-lines[data-cards="always"] .fd-lines-table :is(tbody, tfoot) { display: block; }
393
+ .fd-lines[data-cards="always"] .fd-lines-table thead { display: none; }
394
+ .fd-lines[data-cards="always"] .fd-lines-table tr { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 6px 12px; position: relative; padding: 10px 12px; margin-block-end: 8px; border: 1px solid var(--fd-border); border-radius: var(--fd-radius); background: var(--fd-surface); }
395
+ .fd-lines[data-cards="always"] .fd-lines-table td { display: block; border: none; padding: 0; min-width: 0; }
396
+ .fd-lines[data-cards="always"] .fd-lines-table td[data-label]:not([colspan]:not([colspan="1"]))::before { content: attr(data-label); display: block; font-size: 12px; font-weight: 600; color: var(--fd-muted); }
397
+ .fd-lines[data-cards="always"] .fd-lines-table td.fd-lines-grip, .fd-lines[data-cards="always"] .fd-lines-table td.fd-lines-tools { grid-column: 1 / -1; width: auto; }
398
+ .fd-lines[data-cards="always"] .fd-lines-table td.fd-lines-tools { text-align: end; }
399
+ .fd-line-copy { border: none; background: none; cursor: pointer; color: var(--fd-muted); font-size: 15px; line-height: 1; padding: 4px 6px; border-radius: 4px; }
400
+ .fd-line-copy:hover { color: var(--fd-accent); background: var(--fd-accent-soft); }
308
401
  /* A section heads the lines below it; a note reads as a remark between them. */
309
402
  .fd-line-section td { background: var(--fd-page); }
310
403
  .fd-line-section .fd-input { font-weight: 600; }
@@ -346,25 +439,46 @@ button.fd-chip-label:hover { text-decoration: underline; }
346
439
  .fd-file-confirm:not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
347
440
  .fd-file-note:empty { display: none; }
348
441
  .fd-file-note { color: var(--fd-warning); }
442
+ /* Over the files: how many, and the switch between a list and pictures, at the end. */
443
+ .fd-file-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; justify-self: stretch; }
444
+ .fd-file-views { display: flex; gap: 2px; margin-inline-start: auto; padding: 2px; border: 1px solid var(--fd-border); border-radius: var(--fd-control-radius); background: var(--fd-page); }
445
+ .fd-file-views button {
446
+ display: flex; align-items: center; gap: 6px; min-height: 28px; padding: 2px 10px; font: inherit; font-size: 13px; color: var(--fd-muted); cursor: pointer;
447
+ border: none; border-radius: calc(var(--fd-control-radius) - 2px); background: none;
448
+ }
449
+ .fd-file-views [aria-pressed="true"] { color: var(--fd-text); background: var(--fd-surface); box-shadow: 0 0 0 1px var(--fd-border); }
349
450
  /* Thumbnails: tiles in a row that wraps, the picker the last of them. */
350
451
  .fd-files-thumbs { display: flex; flex-wrap: wrap; align-items: flex-start; }
351
- .fd-files-thumbs > * { flex-basis: 100%; order: 2; }
352
- .fd-files-thumbs > .fd-files { display: contents; }
353
- .fd-files-thumbs .fd-file-item, .fd-image-pick { flex: none; width: 96px; height: 96px; order: 0; }
354
- .fd-files-thumbs > .fd-file-count { order: -1; }
452
+ .fd-files-thumbs > * { flex-basis: 100%; }
453
+ :is(.fd-files-thumbs, .fd-files-cards) > .fd-files { display: contents; }
454
+ .fd-files-thumbs .fd-file-item, .fd-image-pick { flex: none; width: 96px; height: 96px; }
355
455
  .fd-files-thumbs .fd-file-open { height: 100%; padding: 6px; flex-direction: column; justify-content: center; gap: 4px; overflow: hidden; font-size: 12px; }
356
- .fd-files-thumbs .fd-file-thumb { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
456
+ :is(.fd-files-thumbs, .fd-files-cards) .fd-file-thumb { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
357
457
  .fd-files-thumbs .fd-file-name { flex: none; max-width: 100%; }
358
458
  .fd-files-thumbs .fd-file-size, .fd-files-thumbs .fd-file-thumb ~ * { display: none; }
359
- .fd-files-thumbs .fd-file-remove {
459
+ :is(.fd-files-thumbs, .fd-files-cards) .fd-file-remove {
360
460
  position: absolute; inset-block-start: 4px; inset-inline-end: 4px; width: 26px; height: 26px; font-size: 17px;
361
461
  background: rgba(15, 20, 25, 0.62); color: #fff; opacity: 0;
362
462
  }
363
463
  .fd-file-item:is(:hover, :focus-within) .fd-file-remove { opacity: 1; }
464
+ /* A screen with no pointer to hover with shows each tile's remove. */
465
+ @media (hover: none) { .fd-file-item .fd-file-remove { opacity: 1; } }
364
466
  .fd-image-pick {
365
- order: 1; display: grid; place-content: center; justify-items: center; gap: 2px; padding: 6px; text-align: center; font-size: 12px;
467
+ display: grid; place-content: center; justify-items: center; gap: 2px; padding: 6px; text-align: center; font-size: 12px;
366
468
  border: 1px dashed var(--fd-border-strong); border-radius: var(--fd-control-radius); background: var(--fd-page);
367
469
  }
470
+ /* Cards: a picture over each file's name, in two lines at most, and its size; as many to a row as fit. */
471
+ .fd-files-cards { grid-template-columns: repeat(auto-fill, minmax(min(100%, 130px), 1fr)); }
472
+ .fd-files-cards > * { grid-column: 1 / -1; }
473
+ .fd-files-cards > .fd-image-pick { grid-column: auto; justify-self: stretch; width: auto; height: auto; min-height: 96px; }
474
+ .fd-files-cards .fd-file-item { justify-self: stretch; align-items: stretch; }
475
+ .fd-files-cards .fd-file-open { flex-direction: column; align-items: stretch; gap: 2px; padding: 0 0 8px; overflow: hidden; }
476
+ /* The picture fills its box; with none, the kind's page, large, on the box's ground. */
477
+ .fd-file-picture { position: relative; display: grid; place-items: center; aspect-ratio: 4 / 3; margin-block-end: 6px; background: var(--fd-page); border-block-end: 1px solid var(--fd-border); }
478
+ .fd-file-picture .fd-file-icon { width: 44px; height: 56px; padding-block-end: 8px; font-size: 12px; }
479
+ .fd-files-cards :is(.fd-file-name, .fd-file-size) { padding-inline: 8px; }
480
+ .fd-files-cards .fd-file-name { flex: none; flex-wrap: wrap; font-size: 13px; line-height: 1.35; }
481
+ .fd-files-cards .fd-file-name > span { max-width: 100%; }
368
482
  .fd-image-pick .fd-button { border: none; background: none; color: var(--fd-accent); padding: 0; min-height: 0; font-size: 13px; }
369
483
  /* A file open to look at: its name and tools over it, the image whole, a PDF or text filling the box. */
370
484
  .fd-file-viewer { max-width: 1000px; height: min(88vh, 100%); background: var(--fd-surface); }
@@ -400,6 +514,7 @@ button.fd-chip-label:hover { text-decoration: underline; }
400
514
  .fd-richtext-tool svg circle { fill: currentColor; stroke: none; }
401
515
  .fd-link-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px; border: 1px solid var(--fd-border); border-block-end: none; background: var(--fd-surface); }
402
516
  .fd-link-row > .fd-input { flex: 1 1 200px; min-width: 0; }
517
+ .fd-richtext.fd-blank[contenteditable="true"]::before { content: attr(data-placeholder); position: absolute; color: var(--fd-muted); opacity: 0.8; pointer-events: none; }
403
518
  .fd-richtext[contenteditable="false"] { background: transparent; border-color: transparent; padding-inline: 0; min-height: 0; }
404
519
  .fd-richtext p { margin: 0 0 6px; }
405
520
  .fd-code { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px; min-height: 96px; }
@@ -429,12 +544,38 @@ button.fd-chip-label:hover { text-decoration: underline; }
429
544
  .fd-form[data-fd-skin="outlined"] :is(.fd-sections > .fd-section:not([data-style]), .fd-section[data-style="card"][data-on-page]) {
430
545
  background: var(--fd-surface); border: 1px solid var(--fd-border); border-radius: var(--fd-radius); padding: var(--fd-group-pad, 20px 24px);
431
546
  }
432
- .fd-tablist { display: flex; gap: 2px; border-block-end: 1px solid var(--fd-border); overflow-x: auto; }
547
+ /* A saved form placed in another: a quiet line while its page comes, and words in a dashed box when it cannot be shown. */
548
+ .fd-form-part-note { margin: 0; color: var(--fd-muted); font-size: 13px; }
549
+ .fd-form-part-problem { padding: 10px 12px; border: 1px dashed var(--fd-border); border-radius: var(--fd-radius); }
550
+ .fd-tablist { display: flex; gap: 2px; border-block-end: 1px solid var(--fd-border); overflow-x: auto; scrollbar-width: none; }
551
+ .fd-tablist::-webkit-scrollbar { display: none; }
552
+ /* Too long for its room: the tabs fade out at an edge with more past it, under a button that scrolls them along. */
553
+ .fd-tabbar { position: relative; min-width: 0; --fd-fade: to right; }
554
+ [dir="rtl"] .fd-tabbar { --fd-fade: to left; }
555
+ /* Nothing shows under a button (28px); the tabs fade in over the next 36. */
556
+ .fd-tabbar[data-more="start"] > .fd-tablist { mask-image: linear-gradient(var(--fd-fade), transparent 28px, #000 64px); }
557
+ .fd-tabbar[data-more="end"] > .fd-tablist { mask-image: linear-gradient(var(--fd-fade), #000 calc(100% - 64px), transparent calc(100% - 28px)); }
558
+ .fd-tabbar[data-more="start end"] > .fd-tablist { mask-image: linear-gradient(var(--fd-fade), transparent 28px, #000 64px, #000 calc(100% - 64px), transparent calc(100% - 28px)); }
559
+ .fd-tabs-scroll {
560
+ position: absolute; inset-block: 0 1px; z-index: 1; width: 28px; padding: 0; border: none; background: none; cursor: pointer;
561
+ color: var(--fd-muted); display: grid; place-items: center;
562
+ }
563
+ .fd-tabs-scroll[hidden] { display: none; }
564
+ .fd-tabs-scroll:hover { color: var(--fd-accent); }
565
+ .fd-tabs-scroll::before { content: ""; width: 7px; height: 7px; border: solid currentColor; border-width: 0 0 2px 2px; }
566
+ .fd-tabs-before { inset-inline-start: 0; }
567
+ .fd-tabs-after { inset-inline-end: 0; }
568
+ .fd-tabs-before::before { transform: translateX(2px) rotate(45deg); }
569
+ .fd-tabs-after::before { transform: translateX(-2px) rotate(-135deg); }
570
+ [dir="rtl"] .fd-tabs-before::before { transform: translateX(-2px) rotate(-135deg); }
571
+ [dir="rtl"] .fd-tabs-after::before { transform: translateX(2px) rotate(45deg); }
433
572
  .fd-tab {
434
573
  font: inherit; background: none; border: none; cursor: pointer; padding: 8px 14px; color: var(--fd-muted);
435
574
  border-block-end: 2px solid transparent; margin-block-end: -1px; white-space: nowrap;
436
575
  }
437
576
  .fd-tab[aria-selected="true"] { color: var(--fd-accent); border-block-end-color: var(--fd-accent); font-weight: 600; }
577
+ /* A tab with a problem in it, seen or not: a dot in the error colour after its words. */
578
+ .fd-tab[data-problem]::after { content: ""; display: inline-block; width: 6px; height: 6px; margin-inline-start: 6px; border-radius: 50%; background: var(--fd-error); vertical-align: 0.15em; }
438
579
  .fd-tabpanel { padding-block: 16px; display: grid; gap: 16px; }
439
580
 
440
581
  /* ---- buttons ---------------------------------------------------------- */
@@ -482,6 +623,11 @@ button.fd-chip-label:hover { text-decoration: underline; }
482
623
  .fd-sheet-layout.fd-has-side { grid-template-columns: minmax(0, 1fr) minmax(260px, 360px); }
483
624
  /* The side panel goes under the sheet before the sheet is left too narrow for a label beside its value. */
484
625
  @container (max-width: 1000px) { .fd-sheet-layout.fd-has-side { grid-template-columns: minmax(0, 1fr); } }
626
+ /* A part hidden at some widths of the form (hideOn): narrow up to 520px, medium up to 760px, wide above — the form's own width, as its columns. */
627
+ .fd-form { container-name: fd-form; }
628
+ @container fd-form (max-width: 520px) { .fd-form [data-hide-on~="narrow"] { display: none !important; } }
629
+ @container fd-form (min-width: 521px) and (max-width: 760px) { .fd-form [data-hide-on~="medium"] { display: none !important; } }
630
+ @container fd-form (min-width: 761px) { .fd-form [data-hide-on~="wide"] { display: none !important; } }
485
631
  .fd-card {
486
632
  position: relative; background: var(--fd-surface); border: 1px solid var(--fd-border);
487
633
  border-radius: var(--fd-radius); padding: 24px 28px 28px; display: grid; gap: 18px; min-width: 0;
@@ -491,14 +637,15 @@ button.fd-chip-label:hover { text-decoration: underline; }
491
637
  .fd-ribbon-frame { position: absolute; inset: 0; overflow: hidden; border-radius: inherit; pointer-events: none; }
492
638
  .fd-ribbon {
493
639
  position: absolute; inset-block-start: 18px; inset-inline-end: -42px; transform: rotate(45deg); width: 160px;
494
- text-align: center; font-size: 12px; font-weight: 700; padding: 4px 0; color: #fff; background: var(--fd-muted);
640
+ text-align: center; font-size: 12px; font-weight: 700; padding: 4px 0; color: #fff; background: #5f6670;
495
641
  text-transform: uppercase; letter-spacing: 0.06em; pointer-events: none;
496
642
  }
497
643
  [dir="rtl"] .fd-ribbon { transform: rotate(-45deg); }
498
- .fd-ribbon.fd-tone-danger { background: var(--fd-error); }
499
- .fd-ribbon.fd-tone-success { background: var(--fd-success); }
500
- .fd-ribbon.fd-tone-warning { background: #d97706; }
501
- .fd-ribbon.fd-tone-info { background: var(--fd-info); }
644
+ /* White words on a ribbon in either scheme: fixed, deep colours, each at least 5:1 against white. */
645
+ .fd-ribbon.fd-tone-danger { background: #c63c3d; }
646
+ .fd-ribbon.fd-tone-success { background: #2e7d32; }
647
+ .fd-ribbon.fd-tone-warning { background: #a14f00; }
648
+ .fd-ribbon.fd-tone-info { background: #1365d9; }
502
649
  .fd-alert { padding: 10px 14px; border-radius: var(--fd-control-radius); border: 1px solid; font-size: 13.5px; display: flex; align-items: flex-start; gap: 10px; }
503
650
  .fd-alert-message { flex: 1 1 auto; min-width: 0; }
504
651
  .fd-alert-close { border: none; background: none; color: inherit; cursor: pointer; font: inherit; font-size: 18px; line-height: 1; padding: 0 4px; border-radius: 4px; opacity: 0.75; }
@@ -565,6 +712,23 @@ button.fd-chip-label:hover { text-decoration: underline; }
565
712
  .fd-progress { display: grid; gap: 8px; }
566
713
  .fd-progress-text { color: var(--fd-muted); font-size: 13px; }
567
714
  .fd-value-text { display: block; padding-block: 6px; font-variant-numeric: tabular-nums; }
715
+ /* A field's own value in a tone while a condition holds, and in bold (Flectra's decoration-* on a field). */
716
+ .fd-field[data-tone="info"] { --fd-tone: var(--fd-info); --fd-tone-soft: var(--fd-info-soft); }
717
+ .fd-field[data-tone="success"] { --fd-tone: var(--fd-success); --fd-tone-soft: var(--fd-success-soft); }
718
+ .fd-field[data-tone="warning"] { --fd-tone: var(--fd-warning); --fd-tone-soft: var(--fd-warning-soft); }
719
+ .fd-field[data-tone="danger"] { --fd-tone: var(--fd-error); --fd-tone-soft: var(--fd-error-soft); }
720
+ .fd-field[data-tone="muted"] { --fd-tone: var(--fd-muted); --fd-tone-soft: color-mix(in srgb, var(--fd-muted) 14%, transparent); }
721
+ .fd-field[data-tone] :is(input, select, textarea, .fd-value-text, .fd-pending) { color: var(--fd-tone); -webkit-text-fill-color: var(--fd-tone); }
722
+ .fd-field.fd-value-bold :is(input, select, textarea, .fd-value-text, .fd-pending, .fd-value-badge) { font-weight: 700; }
723
+ /* A value as a pill (widget badge): grey, or its tone's colour on its soft shade. */
724
+ .fd-value-badge { justify-self: start; align-self: center; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 10px; border-radius: 999px; font-size: 12.5px; line-height: 18px; font-weight: 500; color: var(--fd-tone, var(--fd-muted)); background: var(--fd-tone-soft, color-mix(in srgb, var(--fd-tone, var(--fd-muted)) 14%, transparent)); }
725
+ .fd-value-badge[hidden] { display: none; }
726
+ /* A value given its own tone by options.tones, over the part's. */
727
+ .fd-value-badge[data-tone="info"] { --fd-tone: var(--fd-info); --fd-tone-soft: var(--fd-info-soft); }
728
+ .fd-value-badge[data-tone="success"] { --fd-tone: var(--fd-success); --fd-tone-soft: var(--fd-success-soft); }
729
+ .fd-value-badge[data-tone="warning"] { --fd-tone: var(--fd-warning); --fd-tone-soft: var(--fd-warning-soft); }
730
+ .fd-value-badge[data-tone="danger"] { --fd-tone: var(--fd-error); --fd-tone-soft: var(--fd-error-soft); }
731
+ .fd-value-badge[data-tone="muted"] { --fd-tone: var(--fd-muted); --fd-tone-soft: color-mix(in srgb, var(--fd-muted) 14%, transparent); }
568
732
  .fd-date-pick { position: relative; display: flex; align-items: center; gap: 6px; min-width: 0; }
569
733
  .fd-date-pick > .fd-input { flex: 1 1 auto; min-width: 0; }
570
734
  .fd-calendar-button {
@@ -696,8 +860,34 @@ button.fd-chip-label:hover { text-decoration: underline; }
696
860
  .fd-dialog {
697
861
  background: var(--fd-surface); color: var(--fd-text); border-radius: max(var(--fd-radius), 6px); max-width: 420px; width: 100%;
698
862
  padding: 20px 22px; display: grid; gap: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); font-family: var(--fd-font);
863
+ /* An edge of its own: in the dark scheme a shadow is lost on the dimmed page, so the box would barely stand out. */
864
+ border: 1px solid var(--fd-edge, transparent);
699
865
  }
700
866
  .fd-dialog p { margin: 0; }
867
+ /* Words a step says: toasts at the foot of the screen, one over another, each in its tone; over the page and over a dialog or panel, whose own words they are too. */
868
+ .fd-says {
869
+ position: fixed; inset-inline: 16px; inset-block-end: max(20px, env(safe-area-inset-bottom, 0px)); z-index: 1100;
870
+ display: grid; justify-items: center; gap: 8px; pointer-events: none;
871
+ }
872
+ .fd-say {
873
+ pointer-events: auto; display: flex; align-items: center; gap: 8px; max-width: 420px;
874
+ padding-block: 8px; padding-inline: 14px 6px; border: 1px solid var(--fd-border); border-inline-start: 4px solid var(--fd-tone);
875
+ border-radius: max(var(--fd-control-radius), 6px); background: var(--fd-surface); color: var(--fd-text);
876
+ box-shadow: 0 8px 24px rgba(15, 20, 25, 0.25); font-size: 13.5px;
877
+ }
878
+ .fd-say > span { flex: 1 1 auto; min-width: 0; }
879
+ /* A button whose steps are running: busy, a turning ring beside its words, not pressed again. */
880
+ .fd-button[aria-busy="true"], .fd-stat[aria-busy="true"] { cursor: progress; }
881
+ .fd-button[aria-busy="true"]::after {
882
+ content: ""; width: 0.85em; height: 0.85em; border: 2px solid currentColor; border-inline-end-color: transparent; border-radius: 50%;
883
+ animation: fd-spin 0.8s linear infinite;
884
+ }
885
+ @keyframes fd-spin { to { rotate: 1turn; } }
886
+ /* A page opened in a form's place: Back over it, the way the page reads. */
887
+ .fd-back { margin: 8px 12px; }
888
+ .fd-back::before { content: "←"; }
889
+ .fd-back:dir(rtl)::before { content: "→"; }
890
+ .fd-form[data-scheme] > .fd-back { margin: 12px 20px 0; }
701
891
  /* A page in a dialog: a head with its title and ×, a body that scrolls, a foot with Discard and Save & Close. */
702
892
  .fd-form-dialog {
703
893
  display: flex; flex-direction: column; width: 100%; max-height: min(90vh, 100%); min-height: 0;
@@ -708,8 +898,10 @@ button.fd-chip-label:hover { text-decoration: underline; }
708
898
  .fd-form-dialog.fd-size-small { max-width: 440px; }
709
899
  .fd-form-dialog.fd-size-medium { max-width: 760px; }
710
900
  .fd-form-dialog.fd-size-large { max-width: 1080px; }
711
- .fd-form-dialog-backdrop:has(> .fd-size-full) { padding: 0; }
712
- .fd-form-dialog.fd-size-full { max-width: none; max-height: none; height: 100%; border-radius: 0; }
901
+ /* A dialog of the full size, and a panel (below): the whole height, square, nothing round it. */
902
+ .fd-form-dialog-backdrop:has(> .fd-size-full), .fd-form-panel-backdrop { padding: 0; }
903
+ .fd-form-dialog:is(.fd-size-full, .fd-form-panel) { max-height: none; height: 100%; border-radius: 0; }
904
+ .fd-size-full { max-width: none; }
713
905
  .fd-form-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-block-end: 1px solid var(--fd-border); background: var(--fd-surface); }
714
906
  .fd-form-dialog-title { margin: 0; font-size: 16px; font-weight: 600; }
715
907
  .fd-dialog-close { border: none; background: none; cursor: pointer; font-size: 22px; line-height: 1; padding: 2px 8px; border-radius: 4px; color: var(--fd-muted); }
@@ -730,10 +922,33 @@ button.fd-chip-label:hover { text-decoration: underline; }
730
922
  /* A page with no sheet of its own sits on the dialog's surface, clear of its edges. */
731
923
  .fd-form-dialog-body:not(:has(.fd-sheet-layout)) { background: var(--fd-surface); padding: 16px 20px 20px; }
732
924
  .fd-form-dialog-foot { padding: 12px 16px; border-block-start: 1px solid var(--fd-border); background: var(--fd-surface); margin: 0; }
733
- @media (prefers-reduced-motion: reduce) { .fd-form *, .fd-form *::before, .fd-form *::after { transition: none !important; } }
925
+ /* A page in a side panel: at the inline-end edge unless it names another, the page behind dimmed, sliding in from its edge; on a phone, the whole screen. */
926
+ .fd-form-panel-backdrop { justify-items: end; }
927
+ .fd-form-panel { max-width: 560px; position: relative; }
928
+ /* Its question before changes are dropped sits over the panel, not over the page. */
929
+ .fd-form-panel > .fd-dialog-backdrop { position: absolute; }
930
+ .fd-width-narrow { max-width: 420px; }
931
+ .fd-width-wide { max-width: 720px; }
932
+ /* The other edges: the start of the line as the page reads; the left, right, top or bottom whatever it reads, the top and bottom the whole width.
933
+ Each says where its slide starts (--fd-from) and which edge it steps back from (--fd-back): the right unless told. */
934
+ .fd-form-panel[data-side="start"] { justify-self: start; }
935
+ .fd-form-panel[data-side="left"] { justify-self: left; }
936
+ .fd-form-panel[data-side="right"] { justify-self: right; }
937
+ .fd-form-panel[data-side="top"] { align-self: start; --fd-from: 0 -100%; --fd-back: 48px 0 0; }
938
+ .fd-form-panel[data-side="bottom"] { align-self: end; --fd-from: 0 100%; --fd-back: 0 0 48px; }
939
+ .fd-form-panel[data-side="end"]:dir(rtl), .fd-form-panel[data-side="start"]:dir(ltr), .fd-form-panel[data-side="left"] { --fd-from: -100% 0; --fd-back: 0 0 0 48px; }
940
+ .fd-form-panel:is([data-side="top"], [data-side="bottom"]) { max-width: none; height: 60%; }
941
+ .fd-form-panel.fd-height-short { height: 40%; }
942
+ .fd-form-panel.fd-height-tall { height: 85%; }
943
+ /* A panel opened from another: the older one steps back from its own edge, still in sight. */
944
+ .fd-form-panel[data-behind] { margin: var(--fd-back, 0 48px 0 0); }
945
+ @media (max-width: 600px) { .fd-form-dialog.fd-form-panel[data-side] { max-width: none; height: 100%; margin: 0; --fd-from: 0 100%; } .fd-form-dialog.fd-form-panel[data-side="top"] { --fd-from: 0 -100%; } }
946
+ @media (prefers-reduced-motion: no-preference) { .fd-form-panel { animation: fd-slide 0.25s ease-out; transition: margin 0.2s; } }
947
+ @keyframes fd-slide { from { translate: var(--fd-from, 100% 0); } }
948
+ @media (prefers-reduced-motion: reduce) { .fd-form *, .fd-form *::before, .fd-form *::after { transition: none !important; } .fd-button[aria-busy="true"]::after { animation: none; } }
734
949
  `;
735
950
  /** Fieldia's whole stylesheet: the skins, then the layout over them, the inputs' details, the question kinds and the choices' details. */
736
- export const FIELDIA_CSS = SKINS_CSS + LAYOUT_CSS + INPUTS_CSS + KINDS_CSS + CHOICES_CSS;
951
+ export const FIELDIA_CSS = SKINS_CSS + LAYOUT_CSS + INPUTS_CSS + SHEET_CSS + BUSINESS_CSS + KINDS_CSS + CHOICES_CSS + PARTS_CSS;
737
952
  const STYLE_ID = 'fieldia-styles';
738
953
  /** Add Fieldia's stylesheet to a document, once. */
739
954
  export function installStyles(document) {
@@ -1 +1 @@
1
- {"version":3,"file":"styles.mjs","sourceRoot":"","sources":["../../../../../libs/widgets/src/lib/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,+FAA+F;AAC/F,MAAM,SAAS,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2sB3B,CAAC;AAEF,0IAA0I;AAC1I,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,WAAW,CAAC;AAEzF,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,QAAkB;IAC9C,IAAI,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC;QAAE,OAAO;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,GAAG,QAAQ,CAAC;IACpB,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"styles.mjs","sourceRoot":"","sources":["../../../../../libs/widgets/src/lib/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAW,CAAC;AAEjE,+FAA+F;AAC/F,MAAM,SAAS,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAw5B3B,CAAC;AAEF,0IAA0I;AAC1I,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhI,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,QAAkB;IAC9C,IAAI,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC;QAAE,OAAO;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,GAAG,QAAQ,CAAC;IACpB,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { WidgetFactory } from './widgets.mjs';
2
+ export declare const taxTotalsWidget: WidgetFactory;
@@ -0,0 +1,2 @@
1
+ import type { WidgetFactory } from './widgets';
2
+ export declare const taxTotalsWidget: WidgetFactory;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.taxTotalsWidget = void 0;
4
+ const kind_parts_1 = require("./kind-parts");
5
+ const numbers_1 = require("./numbers");
6
+ const num = (value) => (typeof value === 'number' ? value : Number(value) || 0);
7
+ /** Flectra's tax_totals, or the plainer value, as subtotals with their groups and the total. */
8
+ function read(value, untaxedWords) {
9
+ if (!value || typeof value !== 'object' || Array.isArray(value))
10
+ return null;
11
+ const v = value;
12
+ if (Array.isArray(v['subtotals']) || v['groups_by_subtotal']) {
13
+ const by = (v['groups_by_subtotal'] ?? {});
14
+ const listed = v['subtotals'] ?? [{ name: untaxedWords, amount: num(v['amount_untaxed']) }];
15
+ const order = v['subtotals_order'] ?? listed.map((s) => s.name);
16
+ return {
17
+ subtotals: order.map((name) => ({
18
+ name,
19
+ amount: num(listed.find((s) => s.name === name)?.amount ?? v['amount_untaxed']),
20
+ groups: (by[name] ?? []).map((g, i) => ({ name: String(g['tax_group_name'] ?? ''), amount: num(g['tax_group_amount']), at: [name, i] })),
21
+ })),
22
+ total: num(v['amount_total']),
23
+ };
24
+ }
25
+ const groups = (Array.isArray(v['groups']) ? v['groups'] : []).map((g, i) => ({ name: String(g['name'] ?? ''), amount: num(g['amount']), at: i }));
26
+ return { subtotals: [{ name: untaxedWords, amount: num(v['untaxed']), groups }], total: num(v['total']) };
27
+ }
28
+ const taxTotalsWidget = ({ form, name, node, id, document, labels, locale = 'en' }) => {
29
+ const make = (0, kind_parts_1.maker)(document);
30
+ const words = (0, kind_parts_1.wordsFor)(labels, locale);
31
+ const options = node.options ?? {};
32
+ const currencyField = typeof options['currencyField'] === 'string' ? options['currencyField'] : null;
33
+ const fixed = typeof options['currency'] === 'string' ? options['currency'] : null;
34
+ const body = make('tbody');
35
+ const element = make('table', { id, class: 'fd-tax-totals' }, body);
36
+ let drawn = '';
37
+ /** The value with one group's amount typed in, and the total following. */
38
+ function typed(group, amount) {
39
+ const value = JSON.parse(JSON.stringify(form.getState().values[name]));
40
+ const was = group.amount;
41
+ if (Array.isArray(group.at)) {
42
+ const [subtotal, i] = group.at;
43
+ (value['groups_by_subtotal'][subtotal][i])['tax_group_amount'] = amount;
44
+ value['amount_total'] = Math.round((num(value['amount_total']) + amount - was) * 100) / 100;
45
+ }
46
+ else {
47
+ value['groups'][group.at]['amount'] = amount;
48
+ value['total'] = Math.round((num(value['total']) + amount - was) * 100) / 100;
49
+ }
50
+ form.setValue(name, value);
51
+ }
52
+ return {
53
+ element,
54
+ focus: () => element.querySelector('input')?.focus(),
55
+ update(state) {
56
+ const totals = read(state.value, words.untaxed);
57
+ const holder = currencyField ? state.values[currencyField] : null;
58
+ const code = fixed ?? (holder && typeof holder === 'object' && 'label' in holder ? holder.label : typeof holder === 'string' ? holder : '');
59
+ const money = (n) => (/^[A-Z]{3}$/.test(code) ? (0, numbers_1.formatMoney)(n, code, 2, locale) : (0, numbers_1.formatNumber)(n, 2, locale));
60
+ const editable = options['editable'] === true && !state.readonly;
61
+ const key = JSON.stringify([totals, code, editable]);
62
+ // Drawn again only when what it shows changes, so a box being typed in stays.
63
+ if (key === drawn || element.contains(document.activeElement))
64
+ return;
65
+ drawn = key;
66
+ const row = (label, amount, className) => make('tr', { class: className }, make('th', { scope: 'row' }, label), make('td', {}, amount));
67
+ const rows = [];
68
+ for (const subtotal of totals?.subtotals ?? []) {
69
+ rows.push(row(subtotal.name, money(subtotal.amount), 'fd-tax-subtotal'));
70
+ for (const group of subtotal.groups) {
71
+ if (!editable) {
72
+ rows.push(row(group.name, money(group.amount), 'fd-tax-group'));
73
+ continue;
74
+ }
75
+ const box = make('input', { type: 'text', class: 'fd-input fd-number-input', inputmode: 'decimal', autocomplete: 'off', 'aria-label': group.name });
76
+ box.value = (0, numbers_1.formatNumber)(group.amount, 2, locale);
77
+ box.addEventListener('change', () => {
78
+ const text = (0, numbers_1.normalizeNumber)(box.value.trim(), locale);
79
+ if (/^-?(\d+\.?\d*|\.\d+)$/.test(text))
80
+ typed(group, Number(text));
81
+ });
82
+ rows.push(row(group.name, box, 'fd-tax-group'));
83
+ }
84
+ }
85
+ if (totals)
86
+ rows.push(row(words.total, money(totals.total), 'fd-tax-total'));
87
+ body.replaceChildren(...rows);
88
+ },
89
+ };
90
+ };
91
+ exports.taxTotalsWidget = taxTotalsWidget;
92
+ //# sourceMappingURL=tax-totals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-totals.js","sourceRoot":"","sources":["../../../../../libs/widgets/src/lib/tax-totals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { maker, wordsFor } from './kind-parts.mjs';
2
+ import { formatMoney, formatNumber, normalizeNumber } from './numbers.mjs';
3
+ const num = (value) => (typeof value === 'number' ? value : Number(value) || 0);
4
+ /** Flectra's tax_totals, or the plainer value, as subtotals with their groups and the total. */
5
+ function read(value, untaxedWords) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value))
7
+ return null;
8
+ const v = value;
9
+ if (Array.isArray(v['subtotals']) || v['groups_by_subtotal']) {
10
+ const by = (v['groups_by_subtotal'] ?? {});
11
+ const listed = v['subtotals'] ?? [{ name: untaxedWords, amount: num(v['amount_untaxed']) }];
12
+ const order = v['subtotals_order'] ?? listed.map((s) => s.name);
13
+ return {
14
+ subtotals: order.map((name) => ({
15
+ name,
16
+ amount: num(listed.find((s) => s.name === name)?.amount ?? v['amount_untaxed']),
17
+ groups: (by[name] ?? []).map((g, i) => ({ name: String(g['tax_group_name'] ?? ''), amount: num(g['tax_group_amount']), at: [name, i] })),
18
+ })),
19
+ total: num(v['amount_total']),
20
+ };
21
+ }
22
+ const groups = (Array.isArray(v['groups']) ? v['groups'] : []).map((g, i) => ({ name: String(g['name'] ?? ''), amount: num(g['amount']), at: i }));
23
+ return { subtotals: [{ name: untaxedWords, amount: num(v['untaxed']), groups }], total: num(v['total']) };
24
+ }
25
+ export const taxTotalsWidget = ({ form, name, node, id, document, labels, locale = 'en' }) => {
26
+ const make = maker(document);
27
+ const words = wordsFor(labels, locale);
28
+ const options = node.options ?? {};
29
+ const currencyField = typeof options['currencyField'] === 'string' ? options['currencyField'] : null;
30
+ const fixed = typeof options['currency'] === 'string' ? options['currency'] : null;
31
+ const body = make('tbody');
32
+ const element = make('table', { id, class: 'fd-tax-totals' }, body);
33
+ let drawn = '';
34
+ /** The value with one group's amount typed in, and the total following. */
35
+ function typed(group, amount) {
36
+ const value = JSON.parse(JSON.stringify(form.getState().values[name]));
37
+ const was = group.amount;
38
+ if (Array.isArray(group.at)) {
39
+ const [subtotal, i] = group.at;
40
+ (value['groups_by_subtotal'][subtotal][i])['tax_group_amount'] = amount;
41
+ value['amount_total'] = Math.round((num(value['amount_total']) + amount - was) * 100) / 100;
42
+ }
43
+ else {
44
+ value['groups'][group.at]['amount'] = amount;
45
+ value['total'] = Math.round((num(value['total']) + amount - was) * 100) / 100;
46
+ }
47
+ form.setValue(name, value);
48
+ }
49
+ return {
50
+ element,
51
+ focus: () => element.querySelector('input')?.focus(),
52
+ update(state) {
53
+ const totals = read(state.value, words.untaxed);
54
+ const holder = currencyField ? state.values[currencyField] : null;
55
+ const code = fixed ?? (holder && typeof holder === 'object' && 'label' in holder ? holder.label : typeof holder === 'string' ? holder : '');
56
+ const money = (n) => (/^[A-Z]{3}$/.test(code) ? formatMoney(n, code, 2, locale) : formatNumber(n, 2, locale));
57
+ const editable = options['editable'] === true && !state.readonly;
58
+ const key = JSON.stringify([totals, code, editable]);
59
+ // Drawn again only when what it shows changes, so a box being typed in stays.
60
+ if (key === drawn || element.contains(document.activeElement))
61
+ return;
62
+ drawn = key;
63
+ const row = (label, amount, className) => make('tr', { class: className }, make('th', { scope: 'row' }, label), make('td', {}, amount));
64
+ const rows = [];
65
+ for (const subtotal of totals?.subtotals ?? []) {
66
+ rows.push(row(subtotal.name, money(subtotal.amount), 'fd-tax-subtotal'));
67
+ for (const group of subtotal.groups) {
68
+ if (!editable) {
69
+ rows.push(row(group.name, money(group.amount), 'fd-tax-group'));
70
+ continue;
71
+ }
72
+ const box = make('input', { type: 'text', class: 'fd-input fd-number-input', inputmode: 'decimal', autocomplete: 'off', 'aria-label': group.name });
73
+ box.value = formatNumber(group.amount, 2, locale);
74
+ box.addEventListener('change', () => {
75
+ const text = normalizeNumber(box.value.trim(), locale);
76
+ if (/^-?(\d+\.?\d*|\.\d+)$/.test(text))
77
+ typed(group, Number(text));
78
+ });
79
+ rows.push(row(group.name, box, 'fd-tax-group'));
80
+ }
81
+ }
82
+ if (totals)
83
+ rows.push(row(words.total, money(totals.total), 'fd-tax-total'));
84
+ body.replaceChildren(...rows);
85
+ },
86
+ };
87
+ };
88
+ //# sourceMappingURL=tax-totals.mjs.map