@fieldia/designer 0.19.1 → 0.21.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 (305) hide show
  1. package/package.json +4 -4
  2. package/src/lib/canvas-head.d.mts +22 -0
  3. package/src/lib/canvas-head.d.ts +22 -0
  4. package/src/lib/canvas-head.js +121 -0
  5. package/src/lib/canvas-head.js.map +1 -0
  6. package/src/lib/canvas-head.mjs +118 -0
  7. package/src/lib/canvas-head.mjs.map +1 -0
  8. package/src/lib/canvas-header.js +7 -1
  9. package/src/lib/canvas-header.js.map +1 -1
  10. package/src/lib/canvas-header.mjs +7 -1
  11. package/src/lib/canvas-header.mjs.map +1 -1
  12. package/src/lib/choice-commands.d.mts +2 -1
  13. package/src/lib/choice-commands.d.ts +2 -1
  14. package/src/lib/choice-commands.js +3 -0
  15. package/src/lib/choice-commands.js.map +1 -1
  16. package/src/lib/choice-commands.mjs +3 -0
  17. package/src/lib/choice-commands.mjs.map +1 -1
  18. package/src/lib/chrome.js +11 -0
  19. package/src/lib/chrome.js.map +1 -1
  20. package/src/lib/chrome.mjs +11 -0
  21. package/src/lib/chrome.mjs.map +1 -1
  22. package/src/lib/date-setting.d.mts +49 -0
  23. package/src/lib/date-setting.d.ts +49 -0
  24. package/src/lib/date-setting.js +125 -0
  25. package/src/lib/date-setting.js.map +1 -0
  26. package/src/lib/date-setting.mjs +122 -0
  27. package/src/lib/date-setting.mjs.map +1 -0
  28. package/src/lib/designer.d.mts +16 -4
  29. package/src/lib/designer.d.ts +16 -4
  30. package/src/lib/designer.js +25 -2
  31. package/src/lib/designer.js.map +1 -1
  32. package/src/lib/designer.mjs +25 -2
  33. package/src/lib/designer.mjs.map +1 -1
  34. package/src/lib/group-fold.js +2 -0
  35. package/src/lib/group-fold.js.map +1 -1
  36. package/src/lib/group-fold.mjs +2 -0
  37. package/src/lib/group-fold.mjs.map +1 -1
  38. package/src/lib/head-commands.d.mts +116 -0
  39. package/src/lib/head-commands.d.ts +116 -0
  40. package/src/lib/head-commands.js +504 -0
  41. package/src/lib/head-commands.js.map +1 -0
  42. package/src/lib/head-commands.mjs +497 -0
  43. package/src/lib/head-commands.mjs.map +1 -0
  44. package/src/lib/head-parts.d.mts +15 -0
  45. package/src/lib/head-parts.d.ts +15 -0
  46. package/src/lib/head-parts.js +69 -0
  47. package/src/lib/head-parts.js.map +1 -0
  48. package/src/lib/head-parts.mjs +64 -0
  49. package/src/lib/head-parts.mjs.map +1 -0
  50. package/src/lib/head-properties.d.mts +25 -0
  51. package/src/lib/head-properties.d.ts +25 -0
  52. package/src/lib/head-properties.js +383 -0
  53. package/src/lib/head-properties.js.map +1 -0
  54. package/src/lib/head-properties.mjs +376 -0
  55. package/src/lib/head-properties.mjs.map +1 -0
  56. package/src/lib/header-commands.js +7 -0
  57. package/src/lib/header-commands.js.map +1 -1
  58. package/src/lib/header-commands.mjs +7 -0
  59. package/src/lib/header-commands.mjs.map +1 -1
  60. package/src/lib/header-properties.js +5 -1
  61. package/src/lib/header-properties.js.map +1 -1
  62. package/src/lib/header-properties.mjs +5 -1
  63. package/src/lib/header-properties.mjs.map +1 -1
  64. package/src/lib/inline-settings.js +19 -11
  65. package/src/lib/inline-settings.js.map +1 -1
  66. package/src/lib/inline-settings.mjs +19 -11
  67. package/src/lib/inline-settings.mjs.map +1 -1
  68. package/src/lib/input-commands.js +3 -2
  69. package/src/lib/input-commands.js.map +1 -1
  70. package/src/lib/input-commands.mjs +3 -2
  71. package/src/lib/input-commands.mjs.map +1 -1
  72. package/src/lib/kind-settings-choices.js +16 -7
  73. package/src/lib/kind-settings-choices.js.map +1 -1
  74. package/src/lib/kind-settings-choices.mjs +16 -7
  75. package/src/lib/kind-settings-choices.mjs.map +1 -1
  76. package/src/lib/kind-settings-inputs.js +116 -58
  77. package/src/lib/kind-settings-inputs.js.map +1 -1
  78. package/src/lib/kind-settings-inputs.mjs +116 -58
  79. package/src/lib/kind-settings-inputs.mjs.map +1 -1
  80. package/src/lib/kind-settings-picker.d.mts +4 -0
  81. package/src/lib/kind-settings-picker.d.ts +4 -0
  82. package/src/lib/kind-settings-picker.js +115 -0
  83. package/src/lib/kind-settings-picker.js.map +1 -0
  84. package/src/lib/kind-settings-picker.mjs +112 -0
  85. package/src/lib/kind-settings-picker.mjs.map +1 -0
  86. package/src/lib/kind-settings-structures.js +35 -16
  87. package/src/lib/kind-settings-structures.js.map +1 -1
  88. package/src/lib/kind-settings-structures.mjs +36 -17
  89. package/src/lib/kind-settings-structures.mjs.map +1 -1
  90. package/src/lib/kind-settings.js +47 -43
  91. package/src/lib/kind-settings.js.map +1 -1
  92. package/src/lib/kind-settings.mjs +47 -43
  93. package/src/lib/kind-settings.mjs.map +1 -1
  94. package/src/lib/layout-settings.d.mts +7 -0
  95. package/src/lib/layout-settings.d.ts +7 -0
  96. package/src/lib/layout-settings.js +15 -2
  97. package/src/lib/layout-settings.js.map +1 -1
  98. package/src/lib/layout-settings.mjs +15 -2
  99. package/src/lib/layout-settings.mjs.map +1 -1
  100. package/src/lib/list-canvas.js +18 -7
  101. package/src/lib/list-canvas.js.map +1 -1
  102. package/src/lib/list-canvas.mjs +18 -7
  103. package/src/lib/list-canvas.mjs.map +1 -1
  104. package/src/lib/list-commands.d.mts +24 -1
  105. package/src/lib/list-commands.d.ts +24 -1
  106. package/src/lib/list-commands.js +105 -2
  107. package/src/lib/list-commands.js.map +1 -1
  108. package/src/lib/list-commands.mjs +102 -1
  109. package/src/lib/list-commands.mjs.map +1 -1
  110. package/src/lib/list-properties.js +248 -28
  111. package/src/lib/list-properties.js.map +1 -1
  112. package/src/lib/list-properties.mjs +248 -28
  113. package/src/lib/list-properties.mjs.map +1 -1
  114. package/src/lib/locales/ar/head.d.mts +2 -0
  115. package/src/lib/locales/ar/head.d.ts +2 -0
  116. package/src/lib/locales/ar/head.js +97 -0
  117. package/src/lib/locales/ar/head.js.map +1 -0
  118. package/src/lib/locales/ar/head.mjs +94 -0
  119. package/src/lib/locales/ar/head.mjs.map +1 -0
  120. package/src/lib/locales/ar/index.js +4 -1
  121. package/src/lib/locales/ar/index.js.map +1 -1
  122. package/src/lib/locales/ar/index.mjs +4 -1
  123. package/src/lib/locales/ar/index.mjs.map +1 -1
  124. package/src/lib/locales/ar/inputs.d.mts +2 -0
  125. package/src/lib/locales/ar/inputs.d.ts +2 -0
  126. package/src/lib/locales/ar/inputs.js +20 -0
  127. package/src/lib/locales/ar/inputs.js.map +1 -0
  128. package/src/lib/locales/ar/inputs.mjs +17 -0
  129. package/src/lib/locales/ar/inputs.mjs.map +1 -0
  130. package/src/lib/locales/ar/list.js +31 -0
  131. package/src/lib/locales/ar/list.js.map +1 -1
  132. package/src/lib/locales/ar/list.mjs +31 -0
  133. package/src/lib/locales/ar/list.mjs.map +1 -1
  134. package/src/lib/locales/ar/panel.js +38 -0
  135. package/src/lib/locales/ar/panel.js.map +1 -1
  136. package/src/lib/locales/ar/panel.mjs +38 -0
  137. package/src/lib/locales/ar/panel.mjs.map +1 -1
  138. package/src/lib/locales/ar/questions.js +8 -0
  139. package/src/lib/locales/ar/questions.js.map +1 -1
  140. package/src/lib/locales/ar/questions.mjs +8 -0
  141. package/src/lib/locales/ar/questions.mjs.map +1 -1
  142. package/src/lib/locales/ar/refusals.js +11 -0
  143. package/src/lib/locales/ar/refusals.js.map +1 -1
  144. package/src/lib/locales/ar/refusals.mjs +11 -0
  145. package/src/lib/locales/ar/refusals.mjs.map +1 -1
  146. package/src/lib/locales/ar/work-text.d.mts +3 -0
  147. package/src/lib/locales/ar/work-text.d.ts +3 -0
  148. package/src/lib/locales/ar/work-text.js +58 -0
  149. package/src/lib/locales/ar/work-text.js.map +1 -0
  150. package/src/lib/locales/ar/work-text.mjs +55 -0
  151. package/src/lib/locales/ar/work-text.mjs.map +1 -0
  152. package/src/lib/locales/en/head.d.mts +101 -0
  153. package/src/lib/locales/en/head.d.ts +101 -0
  154. package/src/lib/locales/en/head.js +106 -0
  155. package/src/lib/locales/en/head.js.map +1 -0
  156. package/src/lib/locales/en/head.mjs +103 -0
  157. package/src/lib/locales/en/head.mjs.map +1 -0
  158. package/src/lib/locales/en/index.d.mts +237 -0
  159. package/src/lib/locales/en/index.d.ts +237 -0
  160. package/src/lib/locales/en/index.js +4 -1
  161. package/src/lib/locales/en/index.js.map +1 -1
  162. package/src/lib/locales/en/index.mjs +4 -1
  163. package/src/lib/locales/en/index.mjs.map +1 -1
  164. package/src/lib/locales/en/inputs.d.mts +26 -0
  165. package/src/lib/locales/en/inputs.d.ts +26 -0
  166. package/src/lib/locales/en/inputs.js +30 -0
  167. package/src/lib/locales/en/inputs.js.map +1 -0
  168. package/src/lib/locales/en/inputs.mjs +27 -0
  169. package/src/lib/locales/en/inputs.mjs.map +1 -0
  170. package/src/lib/locales/en/list.d.mts +48 -0
  171. package/src/lib/locales/en/list.d.ts +48 -0
  172. package/src/lib/locales/en/list.js +33 -0
  173. package/src/lib/locales/en/list.js.map +1 -1
  174. package/src/lib/locales/en/list.mjs +33 -0
  175. package/src/lib/locales/en/list.mjs.map +1 -1
  176. package/src/lib/locales/en/panel.d.mts +6 -0
  177. package/src/lib/locales/en/panel.d.ts +6 -0
  178. package/src/lib/locales/en/panel.js +3 -0
  179. package/src/lib/locales/en/panel.js.map +1 -1
  180. package/src/lib/locales/en/panel.mjs +3 -0
  181. package/src/lib/locales/en/panel.mjs.map +1 -1
  182. package/src/lib/locales/en/questions.d.mts +8 -0
  183. package/src/lib/locales/en/questions.d.ts +8 -0
  184. package/src/lib/locales/en/questions.js +9 -0
  185. package/src/lib/locales/en/questions.js.map +1 -1
  186. package/src/lib/locales/en/questions.mjs +9 -0
  187. package/src/lib/locales/en/questions.mjs.map +1 -1
  188. package/src/lib/locales/en/refusals.d.mts +11 -0
  189. package/src/lib/locales/en/refusals.d.ts +11 -0
  190. package/src/lib/locales/en/refusals.js +11 -0
  191. package/src/lib/locales/en/refusals.js.map +1 -1
  192. package/src/lib/locales/en/refusals.mjs +11 -0
  193. package/src/lib/locales/en/refusals.mjs.map +1 -1
  194. package/src/lib/locales/en/work-text.d.mts +58 -0
  195. package/src/lib/locales/en/work-text.d.ts +58 -0
  196. package/src/lib/locales/en/work-text.js +67 -0
  197. package/src/lib/locales/en/work-text.js.map +1 -0
  198. package/src/lib/locales/en/work-text.mjs +64 -0
  199. package/src/lib/locales/en/work-text.mjs.map +1 -0
  200. package/src/lib/number-box.d.mts +66 -0
  201. package/src/lib/number-box.d.ts +66 -0
  202. package/src/lib/number-box.js +153 -0
  203. package/src/lib/number-box.js.map +1 -0
  204. package/src/lib/number-box.mjs +149 -0
  205. package/src/lib/number-box.mjs.map +1 -0
  206. package/src/lib/page-json.d.mts +3 -1
  207. package/src/lib/page-json.d.ts +3 -1
  208. package/src/lib/page-json.js +5 -3
  209. package/src/lib/page-json.js.map +1 -1
  210. package/src/lib/page-json.mjs +5 -3
  211. package/src/lib/page-json.mjs.map +1 -1
  212. package/src/lib/page-tree.js +11 -1
  213. package/src/lib/page-tree.js.map +1 -1
  214. package/src/lib/page-tree.mjs +11 -1
  215. package/src/lib/page-tree.mjs.map +1 -1
  216. package/src/lib/panel-block.js +22 -6
  217. package/src/lib/panel-block.js.map +1 -1
  218. package/src/lib/panel-block.mjs +22 -6
  219. package/src/lib/panel-block.mjs.map +1 -1
  220. package/src/lib/panel-field.js +5 -1
  221. package/src/lib/panel-field.js.map +1 -1
  222. package/src/lib/panel-field.mjs +5 -1
  223. package/src/lib/panel-field.mjs.map +1 -1
  224. package/src/lib/panel-group.js +7 -3
  225. package/src/lib/panel-group.js.map +1 -1
  226. package/src/lib/panel-group.mjs +7 -3
  227. package/src/lib/panel-group.mjs.map +1 -1
  228. package/src/lib/panel-layout.js +20 -15
  229. package/src/lib/panel-layout.js.map +1 -1
  230. package/src/lib/panel-layout.mjs +20 -15
  231. package/src/lib/panel-layout.mjs.map +1 -1
  232. package/src/lib/panel-look.js +28 -14
  233. package/src/lib/panel-look.js.map +1 -1
  234. package/src/lib/panel-look.mjs +28 -14
  235. package/src/lib/panel-look.mjs.map +1 -1
  236. package/src/lib/panel-map.js +53 -20
  237. package/src/lib/panel-map.js.map +1 -1
  238. package/src/lib/panel-map.mjs +53 -20
  239. package/src/lib/panel-map.mjs.map +1 -1
  240. package/src/lib/panel-page.js +5 -1
  241. package/src/lib/panel-page.js.map +1 -1
  242. package/src/lib/panel-page.mjs +5 -1
  243. package/src/lib/panel-page.mjs.map +1 -1
  244. package/src/lib/panel-parts.js +5 -1
  245. package/src/lib/panel-parts.js.map +1 -1
  246. package/src/lib/panel-parts.mjs +5 -1
  247. package/src/lib/panel-parts.mjs.map +1 -1
  248. package/src/lib/picker-commands.d.mts +16 -0
  249. package/src/lib/picker-commands.d.ts +16 -0
  250. package/src/lib/picker-commands.js +61 -0
  251. package/src/lib/picker-commands.js.map +1 -0
  252. package/src/lib/picker-commands.mjs +58 -0
  253. package/src/lib/picker-commands.mjs.map +1 -0
  254. package/src/lib/rules-answers.d.mts +1 -1
  255. package/src/lib/rules-answers.d.ts +1 -1
  256. package/src/lib/rules-answers.js +45 -21
  257. package/src/lib/rules-answers.js.map +1 -1
  258. package/src/lib/rules-answers.mjs +45 -21
  259. package/src/lib/rules-answers.mjs.map +1 -1
  260. package/src/lib/rules-formula-box.d.mts +7 -3
  261. package/src/lib/rules-formula-box.d.ts +7 -3
  262. package/src/lib/rules-formula-box.js +26 -6
  263. package/src/lib/rules-formula-box.js.map +1 -1
  264. package/src/lib/rules-formula-box.mjs +26 -6
  265. package/src/lib/rules-formula-box.mjs.map +1 -1
  266. package/src/lib/rules-formula.js +7 -5
  267. package/src/lib/rules-formula.js.map +1 -1
  268. package/src/lib/rules-formula.mjs +8 -6
  269. package/src/lib/rules-formula.mjs.map +1 -1
  270. package/src/lib/rules-worked-out.js +66 -12
  271. package/src/lib/rules-worked-out.js.map +1 -1
  272. package/src/lib/rules-worked-out.mjs +66 -12
  273. package/src/lib/rules-worked-out.mjs.map +1 -1
  274. package/src/lib/screen-properties.js +9 -2
  275. package/src/lib/screen-properties.js.map +1 -1
  276. package/src/lib/screen-properties.mjs +9 -2
  277. package/src/lib/screen-properties.mjs.map +1 -1
  278. package/src/lib/setting-refusals.d.mts +3 -0
  279. package/src/lib/setting-refusals.d.ts +3 -0
  280. package/src/lib/setting-refusals.js +25 -0
  281. package/src/lib/setting-refusals.js.map +1 -0
  282. package/src/lib/setting-refusals.mjs +22 -0
  283. package/src/lib/setting-refusals.mjs.map +1 -0
  284. package/src/lib/styles-head.d.mts +7 -0
  285. package/src/lib/styles-head.d.ts +7 -0
  286. package/src/lib/styles-head.js +28 -0
  287. package/src/lib/styles-head.js.map +1 -0
  288. package/src/lib/styles-head.mjs +25 -0
  289. package/src/lib/styles-head.mjs.map +1 -0
  290. package/src/lib/styles.d.mts +1 -1
  291. package/src/lib/styles.d.ts +1 -1
  292. package/src/lib/styles.js +9 -2
  293. package/src/lib/styles.js.map +1 -1
  294. package/src/lib/styles.mjs +9 -2
  295. package/src/lib/styles.mjs.map +1 -1
  296. package/src/lib/table-panel.js +16 -5
  297. package/src/lib/table-panel.js.map +1 -1
  298. package/src/lib/table-panel.mjs +16 -5
  299. package/src/lib/table-panel.mjs.map +1 -1
  300. package/src/lib/work-text.d.mts +43 -0
  301. package/src/lib/work-text.d.ts +43 -0
  302. package/src/lib/work-text.js +235 -0
  303. package/src/lib/work-text.js.map +1 -0
  304. package/src/lib/work-text.mjs +227 -0
  305. package/src/lib/work-text.mjs.map +1 -0
@@ -1,7 +1,9 @@
1
1
  import { cardLines } from '@fieldia/core';
2
2
  import { storedAs } from './kinds.mjs';
3
+ import { FILTERABLE, ruleIsGood, widthIsGood } from './list-commands.mjs';
3
4
  import { openMenu } from './menu.mjs';
4
5
  import { whenClicked } from './steps-panel.mjs';
6
+ import { settingNumber } from './number-box.mjs';
5
7
  /**
6
8
  * The panels of a list page: the list's own — the rows a page holds, its
7
9
  * order, where the search looks, its named filters and its groupings — a
@@ -89,7 +91,12 @@ function filterRow(el, designer, id) {
89
91
  const field = fieldOf(current.field);
90
92
  save({ op: test.value, ...(current.value === undefined && field ? { value: firstValue(field) } : {}) });
91
93
  });
94
+ /** The value the filter holds now, as the page last drew it. */
95
+ let heldValue;
96
+ /** Its number box, drawn again when the page changes. */
97
+ let numberShown = null;
92
98
  function valueControl(field, condition) {
99
+ numberShown = null;
93
100
  if (field.type === 'selection' || field.type === 'boolean') {
94
101
  const options = field.type === 'boolean' ? [['true', w.yes], ['false', w.no]] : field.options.map((o) => [String(o.value), o.label]);
95
102
  const box = select(el, w.filterValue, options);
@@ -100,15 +107,21 @@ function filterRow(el, designer, id) {
100
107
  });
101
108
  return box;
102
109
  }
103
- const numeric = ['integer', 'float', 'monetary'].includes(field.type);
104
- const kind = numeric ? 'number' : field.type === 'date' || field.type === 'datetime' ? 'date' : 'text';
105
- const box = el('input', { class: 'fd-input', type: kind, 'aria-label': w.filterValue });
110
+ if (['integer', 'float', 'monetary'].includes(field.type)) {
111
+ // A number read as every box reads one, kept as it is left; a whole number for a whole-number field.
112
+ const number = settingNumber(el, designer, {
113
+ label: w.filterValue,
114
+ integer: field.type === 'integer',
115
+ saved: () => (typeof heldValue === 'number' ? heldValue : null),
116
+ commit: (n) => save({ value: n }),
117
+ });
118
+ numberShown = number;
119
+ number.refresh();
120
+ return number.input;
121
+ }
122
+ const box = el('input', { class: 'fd-input', type: field.type === 'date' || field.type === 'datetime' ? 'date' : 'text', 'aria-label': w.filterValue });
106
123
  box.value = String(condition.value ?? '');
107
- box.addEventListener('input', () => {
108
- if (numeric && box.value.trim() === '')
109
- return;
110
- save({ value: numeric ? Number(box.value) : box.value });
111
- });
124
+ box.addEventListener('input', () => save({ value: box.value }));
112
125
  return box;
113
126
  }
114
127
  return {
@@ -129,11 +142,13 @@ function filterRow(el, designer, id) {
129
142
  which.value = single.field;
130
143
  test.replaceChildren(...testsFor(field, designer.words).map(([op, words]) => el('option', { value: op }, words)));
131
144
  test.value = single.op;
145
+ heldValue = single.value;
132
146
  const key = `${single.field}:${single.op}`;
133
147
  if (key !== valueKey) {
134
148
  valueKey = key;
135
149
  valueBox.replaceChildren(...(single.op === 'set' || single.op === 'notset' ? [] : [valueControl(field, single)]));
136
150
  }
151
+ numberShown?.refresh();
137
152
  },
138
153
  focus() {
139
154
  name.focus();
@@ -185,7 +200,23 @@ function mapSection(el, designer) {
185
200
  // How the map sits in its page: its height, the gestures it takes, the closest it zooms to show its records.
186
201
  const height = el('select', { class: 'fd-input fd-select', 'aria-label': w.mapHeight });
187
202
  const allGestures = el('input', { type: 'checkbox' });
188
- const fitZoom = el('input', { class: 'fd-input', type: 'number', min: '1', max: '22', placeholder: '16', 'aria-label': w.fitZoom });
203
+ // A whole number, 1 to 22, refused with its range rather than brought into it in silence; emptied, the usual 16.
204
+ let zoomHeld = null;
205
+ const fitZoom = settingNumber(el, designer, {
206
+ label: w.fitZoom,
207
+ attrs: { class: 'fd-input', placeholder: '16' },
208
+ integer: true,
209
+ min: 1,
210
+ max: 22,
211
+ optional: true,
212
+ saved: () => zoomHeld,
213
+ commit: (n) => change((map) => {
214
+ if (n === null)
215
+ delete map.fitZoom;
216
+ else
217
+ map.fitZoom = n;
218
+ }),
219
+ });
189
220
  const rows = {
190
221
  lat: prop(el, w.latitude, lat),
191
222
  lng: prop(el, w.longitude, lng),
@@ -198,7 +229,7 @@ function mapSection(el, designer) {
198
229
  areaField: prop(el, w.areaField, areaField),
199
230
  areas: prop(el, w.areas, areas),
200
231
  };
201
- const details = el('div', { class: 'fd-props' }, prop(el, w.where, where), rows.lat, rows.lng, prop(el, w.pinShows, label), prop(el, w.pinLook, look), rows.photo, rows.icon, rows.iconFrom, rows.turn, rows.home, prop(el, w.cardTitle, title), prop(el, w.cardPicture, picture), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.cardShows), cardFields, addToCard), el('label', { class: 'fd-q-required' }, cardLabels, el('span', {}, w.cardLabels)), el('label', { class: 'fd-q-required' }, cardLead, el('span', {}, w.cardLead)), prop(el, w.cardLink, cardLink), el('label', { class: 'fd-q-required' }, cardChoose, el('span', {}, w.cardChoose)), prop(el, w.opensAs, opens), el('label', { class: 'fd-q-required' }, bubbles, el('span', {}, w.bubbles)), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.mapTools), tools), el('label', { class: 'fd-q-required' }, heat, el('span', {}, w.heat)), rows.weight, rows.areas, rows.areaField, areaWhy, prop(el, w.mapHeight, height), el('label', { class: 'fd-q-required' }, allGestures, el('span', {}, w.allGestures)), prop(el, w.fitZoom, fitZoom), el('p', { class: 'fd-properties-hint' }, w.mapNote));
232
+ const details = el('div', { class: 'fd-props' }, prop(el, w.where, where), rows.lat, rows.lng, prop(el, w.pinShows, label), prop(el, w.pinLook, look), rows.photo, rows.icon, rows.iconFrom, rows.turn, rows.home, prop(el, w.cardTitle, title), prop(el, w.cardPicture, picture), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.cardShows), cardFields, addToCard), el('label', { class: 'fd-q-required' }, cardLabels, el('span', {}, w.cardLabels)), el('label', { class: 'fd-q-required' }, cardLead, el('span', {}, w.cardLead)), prop(el, w.cardLink, cardLink), el('label', { class: 'fd-q-required' }, cardChoose, el('span', {}, w.cardChoose)), prop(el, w.opensAs, opens), el('label', { class: 'fd-q-required' }, bubbles, el('span', {}, w.bubbles)), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.mapTools), tools), el('label', { class: 'fd-q-required' }, heat, el('span', {}, w.heat)), rows.weight, rows.areas, rows.areaField, areaWhy, prop(el, w.mapHeight, height), el('label', { class: 'fd-q-required' }, allGestures, el('span', {}, w.allGestures)), prop(el, w.fitZoom, fitZoom.input), el('p', { class: 'fd-properties-hint' }, w.mapNote));
202
233
  const element = el('div', { class: 'fd-prop', 'data-part': 'map' }, el('span', { class: 'fd-prop-name' }, w.map), onRow, noPlace, details);
203
234
  const fieldsOf = (page, kinds) => everyField(page, designer).filter(({ field }) => kinds(field));
204
235
  const fill = (box, choices, none) => box.replaceChildren(...(none === undefined ? [] : [el('option', { value: '' }, none)]), ...choices.map(({ name, field }) => el('option', { value: name }, field.label)));
@@ -231,7 +262,7 @@ function mapSection(el, designer) {
231
262
  delete map.pin;
232
263
  if (card && !Object.keys(card).length)
233
264
  delete map.card;
234
- designer.setListOptions({ map });
265
+ return designer.setListOptions({ map });
235
266
  };
236
267
  const pinOf = (map) => (map.pin ??= {});
237
268
  /** Where a new map puts its records: the first point, or the two numbers named for latitude and longitude. */
@@ -329,13 +360,6 @@ function mapSection(el, designer) {
329
360
  else
330
361
  delete map.gestures;
331
362
  }));
332
- fitZoom.addEventListener('change', () => change((map) => {
333
- const zoom = Math.round(Number(fitZoom.value));
334
- if (fitZoom.value.trim() === '' || !Number.isFinite(zoom))
335
- delete map.fitZoom;
336
- else
337
- map.fitZoom = Math.min(22, Math.max(1, zoom));
338
- }));
339
363
  addToCard.addEventListener('click', () => {
340
364
  const page = designer.getPage();
341
365
  const lines = current()?.card?.fields ?? [];
@@ -431,18 +455,24 @@ function mapSection(el, designer) {
431
455
  height.replaceChildren(...heights.map(([value, text]) => el('option', { value }, text)));
432
456
  height.value = own;
433
457
  allGestures.checked = map.gestures === 'all';
434
- if (!focused(fitZoom))
435
- fitZoom.value = map.fitZoom === undefined ? '' : String(map.fitZoom);
458
+ zoomHeld = map.fitZoom ?? null;
459
+ fitZoom.refresh();
436
460
  },
437
461
  };
438
462
  }
439
463
  // ---- the list --------------------------------------------------------------------------------
440
464
  export function listProperties(el, designer) {
441
465
  const w = designer.words.list;
442
- const size = el('input', { class: 'fd-input', type: 'number', min: '1', max: '500', placeholder: '40', 'aria-label': w.rowsOnPage });
443
- size.addEventListener('input', () => {
444
- if (size.value.trim() !== '')
445
- designer.setListOptions({ pageSize: Number(size.value) });
466
+ // A whole number, 1 to 500, kept as it is left: never the 60 that "600" passes on its way.
467
+ let pageSize = 40;
468
+ const size = settingNumber(el, designer, {
469
+ label: w.rowsOnPage,
470
+ attrs: { class: 'fd-input', placeholder: '40' },
471
+ integer: true,
472
+ min: 1,
473
+ max: 500,
474
+ saved: () => pageSize,
475
+ commit: (n) => n !== null && designer.setListOptions({ pageSize: n }),
446
476
  });
447
477
  const sortBy = el('select', { class: 'fd-input fd-select', 'aria-label': w.sortedBy });
448
478
  const direction = select(el, w.direction, [['asc', w.ascending], ['desc', w.descending]]);
@@ -475,14 +505,35 @@ export function listProperties(el, designer) {
475
505
  onPick: (name) => designer.setListOptions({ groupBy: [...taken, name] }),
476
506
  });
477
507
  });
508
+ // A box to choose each row; the search bar, or a keyword and each field's values; rows under their parent.
509
+ const choose = el('input', { type: 'checkbox', 'aria-label': w.choose });
510
+ choose.addEventListener('change', () => {
511
+ if (!designer.setListOptions({ choose: choose.checked }))
512
+ choose.checked = !choose.checked;
513
+ });
514
+ const chooseRow = el('div', { class: 'fd-prop', 'data-part': 'choose' }, el('label', { class: 'fd-q-required' }, choose, el('span', {}, w.choose)), el('p', { class: 'fd-properties-hint' }, w.chooseHint));
515
+ const sorts = el('input', { type: 'checkbox', 'aria-label': w.sortable });
516
+ sorts.addEventListener('change', () => {
517
+ if (!designer.setListOptions({ sortable: sorts.checked }))
518
+ sorts.checked = !sorts.checked;
519
+ });
520
+ const sortsRow = el('div', { class: 'fd-prop', 'data-part': 'sortable' }, el('label', { class: 'fd-q-required' }, sorts, el('span', {}, w.sortable)), el('p', { class: 'fd-properties-hint' }, w.sortableHint));
521
+ const searchWay = select(el, w.searchWay, [['bar', w.searchWays.bar], ['fields', w.searchWays.fields]]);
522
+ searchWay.addEventListener('change', () => designer.setListOptions({ search: searchWay.value }));
523
+ const filterBox = el('div', { class: 'fd-list-filter-fields' });
524
+ const filterFieldsRow = el('div', { class: 'fd-prop', 'data-part': 'filter-fields' }, el('span', { class: 'fd-prop-name' }, w.filterFields), filterBox, el('p', { class: 'fd-properties-hint' }, w.filterFieldsNote), el('p', { class: 'fd-properties-hint' }, w.filterLabelsNote));
525
+ const parent = el('select', { class: 'fd-input fd-select', 'aria-label': w.parentField });
526
+ parent.addEventListener('change', () => designer.setListOptions({ parentField: parent.value || null }));
527
+ const parentWhy = el('p', { class: 'fd-properties-hint', hidden: '' }, w.noParentField);
528
+ const parentRow = el('label', { class: 'fd-prop', 'data-part': 'parent' }, el('span', { class: 'fd-prop-name' }, w.parentField), parent, parentWhy);
478
529
  const onMap = mapSection(el, designer);
479
- const element = el('div', { class: 'fd-props' }, prop(el, w.rowsOnPage, size), prop(el, w.sortedBy, sortBy), directionRow, el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.searchLooksIn), searchBox, el('p', { class: 'fd-properties-hint' }, w.noneTicked)), el('div', { class: 'fd-prop fd-prop-filters', 'data-part': 'filters' }, el('span', { class: 'fd-prop-name' }, w.filters), filtersBox, addFilter), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.groupBy), groupsBox, addGroup), onMap.element, el('p', { class: 'fd-properties-hint' }, w.rowOpens));
530
+ const element = el('div', { class: 'fd-props' }, prop(el, w.rowsOnPage, size.input), prop(el, w.sortedBy, sortBy), directionRow, chooseRow, sortsRow, prop(el, w.searchWay, searchWay), filterFieldsRow, parentRow, el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.searchLooksIn), searchBox, el('p', { class: 'fd-properties-hint' }, w.noneTicked)), el('div', { class: 'fd-prop fd-prop-filters', 'data-part': 'filters' }, el('span', { class: 'fd-prop-name' }, w.filters), filtersBox, addFilter), el('div', { class: 'fd-prop' }, el('span', { class: 'fd-prop-name' }, w.groupBy), groupsBox, addGroup), onMap.element, el('p', { class: 'fd-properties-hint' }, w.rowOpens));
480
531
  return {
481
532
  element,
482
533
  update(page) {
483
534
  const list = listOf(page);
484
- if (!focused(size))
485
- size.value = list.pageSize === undefined ? '' : String(list.pageSize);
535
+ pageSize = list.pageSize ?? 40;
536
+ size.refresh();
486
537
  const sortable = everyField(page, designer).filter(({ field }) => !UNSORTABLE.has(field.type));
487
538
  sortBy.replaceChildren(el('option', { value: '' }, w.appOrder), ...sortable.map(({ name, field }) => el('option', { value: name }, field.label)));
488
539
  const order = list.sort?.[0];
@@ -519,6 +570,37 @@ export function listProperties(el, designer) {
519
570
  });
520
571
  while (filtersBox.children.length > drawn.length)
521
572
  filtersBox.lastElementChild?.remove();
573
+ choose.checked = list.choose !== false;
574
+ sorts.checked = list.sortable !== false;
575
+ searchWay.value = list.search ?? 'bar';
576
+ // The filter bar's fields: those whose values it can list, ticked in the order the page keeps them.
577
+ filterFieldsRow.hidden = list.search !== 'fields';
578
+ const offered = list.filterFields ?? [];
579
+ const filterable = [...new Set([...offered, ...everyField(page, designer).filter(({ field }) => FILTERABLE.has(field.type)).map(({ name }) => name)])];
580
+ // Each one offered has a name of its own on the bar, its label unless one is written; kept while it is being typed in.
581
+ const typing = filterBox.querySelector('.fd-list-filter-name:focus');
582
+ if (!typing || !filterable.includes(typing.dataset['field'] ?? '')) {
583
+ filterBox.replaceChildren(...filterable.map((name) => {
584
+ const label = page.fields[name]?.label ?? designer.modelFields().find((m) => m.name === name)?.field.label ?? name;
585
+ const box = el('input', { type: 'checkbox', 'aria-label': w.filterFieldNamed(label) });
586
+ box.checked = offered.includes(name);
587
+ box.addEventListener('change', () => designer.setListOptions({ filterFields: box.checked ? [...offered, name] : offered.filter((n) => n !== name) }));
588
+ const choice = el('label', { class: 'fd-q-required' }, box, el('span', {}, label));
589
+ if (!offered.includes(name))
590
+ return choice;
591
+ const own = el('input', { class: 'fd-input fd-list-filter-name', 'data-field': name, 'aria-label': w.filterLabelOf(label), title: w.filterLabelOf(label), placeholder: label });
592
+ own.value = list.filterLabels?.[name] ?? '';
593
+ own.addEventListener('change', () => designer.setListOptions({ filterLabel: { field: name, label: own.value } }));
594
+ return el('div', { class: 'fd-list-filter-field' }, choice, own);
595
+ }));
596
+ }
597
+ // Rows under their parent: a link to the list's own records.
598
+ const own = page.data.kind === 'record' ? page.data.model : '';
599
+ const links = everyField(page, designer).filter(({ field }) => field.type === 'many2one' && field.relation === own);
600
+ parent.replaceChildren(el('option', { value: '' }, w.noParent), ...links.map(({ name, field }) => el('option', { value: name }, field.label)));
601
+ parent.value = list.parentField ?? '';
602
+ parentWhy.hidden = links.length > 0;
603
+ parentRow.hidden = !links.length && !list.parentField;
522
604
  const groups = list.groupBy ?? [];
523
605
  groupsBox.replaceChildren(...groups.map((name) => {
524
606
  const label = page.fields[name]?.label ?? name;
@@ -551,7 +633,8 @@ export function columnProperties(el, designer, name) {
551
633
  left.addEventListener('click', () => designer.moveColumn(name, at() - 1));
552
634
  right.addEventListener('click', () => designer.moveColumn(name, at() + 1));
553
635
  remove.addEventListener('click', () => designer.removeColumn(name));
554
- const element = el('div', { class: 'fd-props' }, about, orderRow, el('div', { class: 'fd-props-actions' }, left, right, remove));
636
+ const cells = cellSettings(el, designer, name);
637
+ const element = el('div', { class: 'fd-props' }, about, orderRow, ...cells.rows, el('div', { class: 'fd-props-actions' }, left, right, remove));
555
638
  return {
556
639
  element,
557
640
  update(page) {
@@ -563,6 +646,7 @@ export function columnProperties(el, designer, name) {
563
646
  orderRow.hidden = UNSORTABLE.has(field.type);
564
647
  const first = list.sort?.[0];
565
648
  order.value = first?.field === name ? (first.desc ? 'desc' : 'asc') : '';
649
+ cells.update(page);
566
650
  const index = list.columns.indexOf(name);
567
651
  left.hidden = index <= 0;
568
652
  right.hidden = index === list.columns.length - 1;
@@ -570,6 +654,142 @@ export function columnProperties(el, designer, name) {
570
654
  },
571
655
  };
572
656
  }
657
+ // ---- a column's cells ------------------------------------------------------------------------------
658
+ /**
659
+ * How a column draws its cells, as a work tracker's backlog does: a state's
660
+ * dot in each value's colour, an icon before the words by another field's
661
+ * value (a bug's, a task's), a width in characters, the side its words keep to.
662
+ */
663
+ function cellSettings(el, designer, name) {
664
+ const w = designer.words.list;
665
+ const cellNow = () => ({ ...listOf(designer.getPage()).cells?.[name] });
666
+ const save = (cell) => {
667
+ for (const key of Object.keys(cell))
668
+ if (cell[key] === undefined)
669
+ delete cell[key];
670
+ return designer.setListCell(name, Object.keys(cell).length ? cell : null);
671
+ };
672
+ const widget = select(el, w.cellWidget, [['', w.cellWidgets.words], ['dot', w.cellWidgets.dot]]);
673
+ widget.addEventListener('change', () => save({ ...cellNow(), widget: widget.value || undefined, ...(widget.value ? {} : { options: undefined }) }));
674
+ const widgetRow = prop(el, w.cellWidget, widget);
675
+ const dots = el('div', { class: 'fd-list-cell-looks' });
676
+ const dotsRow = el('div', { class: 'fd-prop', 'data-part': 'dots' }, dots);
677
+ const lead = el('select', { class: 'fd-input fd-select', 'aria-label': w.leadFrom });
678
+ lead.addEventListener('change', () => save({ ...cellNow(), lead: lead.value ? { field: lead.value, looks: {} } : undefined }));
679
+ const leadLooks = el('div', { class: 'fd-list-cell-looks' });
680
+ const leadRow = el('div', { class: 'fd-prop', 'data-part': 'lead' }, el('span', { class: 'fd-prop-name' }, w.leadFrom), lead, leadLooks);
681
+ // Its width, in characters of its words or in pixels for the whole column: a number and its unit.
682
+ // A whole number: 1 to 200 characters, or 8 to 4000 pixels; kept as it is left, refused with its range; emptied, as its words need.
683
+ const unit = select(el, w.cellWidthUnit, [['ch', w.cellWidthUnits.characters], ['px', w.cellWidthUnits.pixels]]);
684
+ const widthIn = (n) => (unit.value === 'px' ? `${n}px` : n);
685
+ const sizing = {
686
+ label: w.cellWidth,
687
+ integer: true,
688
+ min: 1,
689
+ max: 200,
690
+ optional: true,
691
+ saved: () => {
692
+ const now = cellNow().width;
693
+ return now === undefined ? null : Number.parseInt(String(now), 10);
694
+ },
695
+ commit: (n) => save({ ...cellNow(), width: n === null ? undefined : widthIn(n) }),
696
+ };
697
+ const width = settingNumber(el, designer, sizing);
698
+ const unitRange = () => {
699
+ sizing.min = unit.value === 'px' ? 8 : 1;
700
+ sizing.max = unit.value === 'px' ? 4000 : 200;
701
+ };
702
+ unit.addEventListener('change', () => {
703
+ unitRange();
704
+ // The same number in the other unit, when it is a width in that one too.
705
+ const n = sizing.saved();
706
+ if (typeof n === 'number' && widthIsGood(widthIn(n)))
707
+ save({ ...cellNow(), width: widthIn(n) });
708
+ });
709
+ const widthRow = el('div', { class: 'fd-prop', 'data-part': 'width' }, el('span', { class: 'fd-prop-name' }, w.cellWidth), el('div', { class: 'fd-list-width' }, width.input, unit), el('p', { class: 'fd-properties-hint' }, w.cellWidthHint));
710
+ const side = select(el, w.cellSide, [['', w.cellSides.auto], ['start', w.cellSides.start], ['end', w.cellSides.end]]);
711
+ side.addEventListener('change', () => save({ ...cellNow(), align: (side.value || undefined) }));
712
+ // Its header's rule: at its end, none, or set in from its end by some pixels.
713
+ const rule = select(el, w.cellRule, [['', w.cellRules.end], ['none', w.cellRules.none], ['in', w.cellRules.in]]);
714
+ // Pixels in from the column's end, a whole number 0 to 400, kept as it is left: letters never become 0px.
715
+ const ruleNow = () => {
716
+ const now = cellNow().rule;
717
+ return typeof now === 'string' ? Number.parseInt(now, 10) : null;
718
+ };
719
+ const ruleIn = settingNumber(el, designer, {
720
+ label: w.cellRuleIn,
721
+ integer: true,
722
+ min: 0,
723
+ max: 400,
724
+ saved: ruleNow,
725
+ commit: (n) => n !== null && ruleIsGood(`${n}px`) && save({ ...cellNow(), rule: `${n}px` }),
726
+ });
727
+ rule.addEventListener('change', () => {
728
+ if (rule.value === 'none')
729
+ return void save({ ...cellNow(), rule: false });
730
+ if (rule.value === '')
731
+ return void save({ ...cellNow(), rule: undefined });
732
+ save({ ...cellNow(), rule: `${ruleNow() ?? 0}px` });
733
+ });
734
+ const ruleInRow = prop(el, w.cellRuleIn, ruleIn.input);
735
+ const ruleRow = el('div', { class: 'fd-prop', 'data-part': 'rule' }, el('span', { class: 'fd-prop-name' }, w.cellRule), rule, el('p', { class: 'fd-properties-hint' }, w.cellRuleHint));
736
+ /** A value's colour, and its icon for a lead: a row for each option of a choice. */
737
+ const optionRows = (field, row) => field?.type === 'selection' ? field.options.map((option) => el('div', { class: 'fd-list-cell-look' }, el('span', { class: 'fd-list-cell-look-name' }, option.label), ...row(String(option.value), option.label))) : [];
738
+ return {
739
+ rows: [widgetRow, dotsRow, leadRow, widthRow, prop(el, w.cellSide, side), ruleRow, ruleInRow],
740
+ update(page) {
741
+ const field = page.fields[name];
742
+ const cell = page.layout.type === 'list' ? (page.layout.cells?.[name] ?? {}) : {};
743
+ widget.value = cell.widget === 'dot' ? 'dot' : '';
744
+ widgetRow.hidden = field?.type !== 'selection';
745
+ // Each state's dot colour, when the column draws its dot.
746
+ dotsRow.hidden = cell.widget !== 'dot';
747
+ const colours = (cell.options?.['colours'] ?? {});
748
+ dots.replaceChildren(...optionRows(field, (value, label) => {
749
+ const colour = el('input', { type: 'color', class: 'fd-insp-colour', 'aria-label': w.dotColour(label) });
750
+ colour.value = colours[value] ?? '#7e7e7e';
751
+ colour.addEventListener('input', () => save({ ...cellNow(), options: { ...cellNow().options, colours: { ...(cellNow().options?.['colours'] ?? {}), [value]: colour.value } } }));
752
+ return [colour];
753
+ }));
754
+ // An icon before the words: by a choice's value, each with its icon and colour.
755
+ const choices = everyField(page, designer).filter(({ field: f }) => f.type === 'selection');
756
+ lead.replaceChildren(el('option', { value: '' }, w.noLead), ...choices.map(({ name: n, field: f }) => el('option', { value: n }, f.label)));
757
+ lead.value = cell.lead?.field ?? '';
758
+ const from = cell.lead ? page.fields[cell.lead.field] : undefined;
759
+ leadLooks.replaceChildren(...optionRows(from, (value, label) => {
760
+ const look = cell.lead?.looks[value];
761
+ const icon = el('input', { class: 'fd-input', 'aria-label': w.leadIcon(label), placeholder: 'bug' });
762
+ icon.value = look?.icon ?? '';
763
+ const colour = el('input', { type: 'color', class: 'fd-insp-colour', 'aria-label': w.leadColour(label) });
764
+ colour.value = look?.colour ?? '#7e7e7e';
765
+ const write = () => {
766
+ const now = cellNow();
767
+ if (!now.lead)
768
+ return;
769
+ const looks = { ...now.lead.looks };
770
+ if (icon.value.trim())
771
+ looks[value] = { icon: icon.value.trim(), ...(look?.colour || colour.value !== '#7e7e7e' ? { colour: colour.value } : {}) };
772
+ else
773
+ delete looks[value];
774
+ save({ ...now, lead: { field: now.lead.field, looks } });
775
+ };
776
+ icon.addEventListener('change', write);
777
+ colour.addEventListener('input', write);
778
+ return [icon, colour];
779
+ }));
780
+ const inPixels = typeof cell.width === 'string';
781
+ if (!focused(unit) && cell.width !== undefined)
782
+ unit.value = inPixels ? 'px' : 'ch';
783
+ unitRange();
784
+ width.refresh();
785
+ side.value = cell.align ?? '';
786
+ if (!focused(rule))
787
+ rule.value = cell.rule === false ? 'none' : cell.rule ? 'in' : '';
788
+ ruleIn.refresh();
789
+ ruleInRow.hidden = rule.value !== 'in';
790
+ },
791
+ };
792
+ }
573
793
  // ---- a button for the rows chosen ----------------------------------------------------------------
574
794
  export function listActionProperties(el, designer, id) {
575
795
  const w = designer.words.list;