@xeplr/ui-factory 1.0.14 → 1.1.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.
@@ -13,6 +13,7 @@
13
13
  import { CONTROLS, LABEL_VARIANTS, LIST_ACTIONS, STYLE_KEYS, SCREEN_STYLE_KEYS, LAYOUTS, MULTI_SEPARATOR, MAX_FILE_MB, STEP_LIMIT, acceptList } from './controls.js'
14
14
  import { DOCUMENT_KIND, DOCUMENT_VERSION } from './document.js'
15
15
  import { RESERVED_COLUMNS, MAX_IDENTIFIER } from './tableSchema.js'
16
+ import { FIELD_PRESETS, INPUT_TYPES, presetFor } from './presets.js'
16
17
 
17
18
  const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/
18
19
  const TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_.$-]*$/
@@ -88,6 +89,9 @@ export function validateDocument(doc, controls = CONTROLS) {
88
89
  if (!nonEmptyString(props.label)) err(`${at}.props.label`, 'is required — the text shown above the field')
89
90
  if (props.required !== undefined && typeof props.required !== 'boolean') err(`${at}.props.required`, 'must be true or false')
90
91
  if (props.placeholder !== undefined && typeof props.placeholder !== 'string') err(`${at}.props.placeholder`, 'must be a string')
92
+ // Where the field came from (a ready-made field's key), or false: plain on purpose.
93
+ if (props.preset !== undefined && props.preset !== false && !presetFor(props.preset)) err(`${at}.props.preset`, `"${props.preset}" is not a ready-made field — one of: ${FIELD_PRESETS.map((p) => p.key).join(', ')}, or false`)
94
+ if (props.inputType !== undefined && !INPUT_TYPES.includes(props.inputType)) err(`${at}.props.inputType`, `must be one of: ${INPUT_TYPES.join(', ')}`)
91
95
  checkDefault(node, def, at, err)
92
96
  checkValidation(node, def, at, err)
93
97
  }
@@ -234,7 +238,7 @@ function checkValidation(node, def, at, err) {
234
238
  const val = v[key]
235
239
  if (key === 'minLength' || key === 'maxLength') {
236
240
  if (!(Number.isInteger(val) && val >= 0)) err(path, 'must be a whole number, 0 or more')
237
- } else if (key === 'integer') {
241
+ } else if (key === 'integer' || key === 'notFuture') {
238
242
  if (typeof val !== 'boolean') err(path, 'must be true or false')
239
243
  } else if (key === 'pattern') {
240
244
  try { new RegExp(val) } catch (_) { err(path, `"${val}" is not a valid regular expression`) }
package/src/values.js CHANGED
@@ -32,6 +32,13 @@ export function formSchema(doc, controls = CONTROLS) {
32
32
  const v = p.validation && { ...p.validation }
33
33
  if (v) {
34
34
  delete v.patternMessage
35
+ // "Not in the future" is today's date AT THE MOMENT OF CHECKING — the
36
+ // server builds this schema per save, so it never goes stale.
37
+ if (v.notFuture) {
38
+ const today = localDay()
39
+ if (!v.max || v.max > today) v.max = today
40
+ }
41
+ delete v.notFuture
35
42
  if (Object.keys(v).length) field.validation = v
36
43
  }
37
44
  return field
@@ -130,6 +137,7 @@ export function fieldError(node, value, loadedOptions) {
130
137
  const day = value.slice(0, 10)
131
138
  if (v.min && day < v.min) return `${label} must be on or after ${v.min}`
132
139
  if (v.max && day > v.max) return `${label} must be on or before ${v.max}`
140
+ if (v.notFuture && day > localDay()) return `${label} cannot be in the future`
133
141
  return null
134
142
  }
135
143
  case 'datetime': {
@@ -173,15 +181,20 @@ export function optionValue(options, raw) {
173
181
  return hit ? hit.id : raw
174
182
  }
175
183
 
184
+ /** Today as YYYY-MM-DD, in the local timezone — the day a person means by "today". */
185
+ function localDay() {
186
+ const d = new Date()
187
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
188
+ }
189
+
176
190
  function safeRegExp(pattern) {
177
191
  try { return new RegExp(pattern) } catch (_) { return { test: () => true } }
178
192
  }
179
193
 
180
- // ── autosave ─────────────────────────────────────────────────────────────
181
- // There is no submit. A screen saves itself a moment after a change, as long
182
- // as what is entered is acceptable. This decides whether it can, and which
183
- // messages to SHOW: a field nobody has touched is not shouted at for being
184
- // empty — it only holds the save back.
194
+ // ── can it save? ─────────────────────────────────────────────────────────
195
+ // Save (a button, one AJAX call) goes out only when what is entered is
196
+ // acceptable. This decides whether it can, and which messages to SHOW: a field
197
+ // nobody has touched is not shouted at for being empty until Save is pressed.
185
198
 
186
199
  /**
187
200
  * @param touched Set of field names the person has changed or left