@samline/forms 2.8.0 → 2.9.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.
@@ -1,4 +1,4 @@
1
- import { u as FormsApi, v as FormsAvailable, F as FormTarget, a as FormControllerOptions, b as FormController, N as NewFormInput } from '../registry-GzopYPCR.cjs';
1
+ import { v as FormsApi, w as FormsAvailable, F as FormTarget, a as FormControllerOptions, b as FormController, N as NewFormInput } from '../registry-CPe_lf8-.cjs';
2
2
 
3
3
  declare const Forms: FormsApi;
4
4
  declare const form: (target: FormTarget, options?: FormControllerOptions) => FormController;
@@ -1,4 +1,4 @@
1
- import { u as FormsApi, v as FormsAvailable, F as FormTarget, a as FormControllerOptions, b as FormController, N as NewFormInput } from '../registry-GzopYPCR.js';
1
+ import { v as FormsApi, w as FormsAvailable, F as FormTarget, a as FormControllerOptions, b as FormController, N as NewFormInput } from '../registry-CPe_lf8-.js';
2
2
 
3
3
  declare const Forms: FormsApi;
4
4
  declare const form: (target: FormTarget, options?: FormControllerOptions) => FormController;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  browser
3
- } from "../chunk-23VZAYA3.js";
3
+ } from "../chunk-6MA5TF6R.js";
4
4
 
5
5
  // src/browser/global.ts
6
6
  var Forms = browser;
@@ -651,6 +651,7 @@ var createDestroy = (state2, _helpers) => () => {
651
651
  }
652
652
  }
653
653
  state2.watchedFields.clear();
654
+ state2.groupWatchers.clear();
654
655
  state2.subscribers.clear();
655
656
  state2.submitHandlers.clear();
656
657
  state2.fieldCache.clear();
@@ -847,9 +848,9 @@ var writeFieldValue = (fields, value) => {
847
848
  continue;
848
849
  }
849
850
  if (field instanceof HTMLSelectElement && field.multiple) {
850
- const selectedValues = normalizedArray ?? [normalizedValue];
851
+ const selectedValues2 = normalizedArray ?? [normalizedValue];
851
852
  for (const option of field.options) {
852
- option.selected = selectedValues.includes(option.value);
853
+ option.selected = selectedValues2.includes(option.value);
853
854
  }
854
855
  continue;
855
856
  }
@@ -884,8 +885,7 @@ var submitForm = (form2) => {
884
885
  var createGetValue = (state2, helpers) => (name) => readFieldValue(helpers.getFieldsByName(name));
885
886
 
886
887
  // src/api/observe.ts
887
- var createObserve = (state2, helpers) => (field, callback) => {
888
- if (state2.isDestroyed) return () => void 0;
888
+ var observeField = (state2, field, callback) => {
889
889
  const callbacks = state2.watchedFields.get(field) ?? /* @__PURE__ */ new Set();
890
890
  callbacks.add(callback);
891
891
  state2.watchedFields.set(field, callbacks);
@@ -900,6 +900,26 @@ var createObserve = (state2, helpers) => (field, callback) => {
900
900
  }
901
901
  };
902
902
  };
903
+ var observeFields = (state2, fields, callback) => {
904
+ const names = Array.from(new Set(fields));
905
+ if (names.length === 0) return () => void 0;
906
+ const entry = { fields: names, callback };
907
+ state2.groupWatchers.add(entry);
908
+ if (state2.element) {
909
+ const values = Object.fromEntries(names.map((name) => [name, state2.api.getValue(name)]));
910
+ callback(values, null, state2.element, state2.api.getState());
911
+ }
912
+ return () => {
913
+ state2.groupWatchers.delete(entry);
914
+ };
915
+ };
916
+ var createObserve = (state2, _helpers) => {
917
+ function observe(field, callback) {
918
+ if (state2.isDestroyed) return () => void 0;
919
+ return Array.isArray(field) ? observeFields(state2, field, callback) : observeField(state2, field, callback);
920
+ }
921
+ return observe;
922
+ };
903
923
 
904
924
  // src/api/on-submit.ts
905
925
  var createOnSubmit = (state2, _helpers) => (callback, preventDefault = true) => {
@@ -971,6 +991,75 @@ var createSetErrors = (state2, helpers) => (fields) => {
971
991
  return state2.api;
972
992
  };
973
993
 
994
+ // src/api/set-options.ts
995
+ var isGroup = (entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.options);
996
+ var createOption = (entry) => {
997
+ const option = document.createElement("option");
998
+ if (typeof entry === "object") {
999
+ option.value = String(entry.value);
1000
+ option.textContent = entry.label ?? String(entry.value);
1001
+ option.disabled = Boolean(entry.disabled);
1002
+ if (entry.selected) option.defaultSelected = true;
1003
+ } else {
1004
+ option.value = String(entry);
1005
+ option.textContent = String(entry);
1006
+ }
1007
+ return option;
1008
+ };
1009
+ var buildNodes = (options) => {
1010
+ const nodes = [];
1011
+ let hasExplicitSelection = false;
1012
+ const track = (option) => {
1013
+ if (option.defaultSelected) hasExplicitSelection = true;
1014
+ return option;
1015
+ };
1016
+ for (const entry of options) {
1017
+ if (entry === null || entry === void 0) continue;
1018
+ if (isGroup(entry)) {
1019
+ const group = document.createElement("optgroup");
1020
+ group.label = entry.label;
1021
+ group.disabled = Boolean(entry.disabled);
1022
+ for (const child of entry.options) {
1023
+ if (child === null || child === void 0) continue;
1024
+ group.appendChild(track(createOption(child)));
1025
+ }
1026
+ nodes.push(group);
1027
+ continue;
1028
+ }
1029
+ nodes.push(track(createOption(entry)));
1030
+ }
1031
+ return { nodes, hasExplicitSelection };
1032
+ };
1033
+ var selectedValues = (select) => Array.from(select.selectedOptions, (option) => option.value);
1034
+ var restoreSelection = (select, previous) => {
1035
+ if (previous.length === 0) return;
1036
+ const options = Array.from(select.options);
1037
+ if (select.multiple) {
1038
+ for (const option of options) option.selected = previous.includes(option.value);
1039
+ return;
1040
+ }
1041
+ const match = options.find((option) => option.value === previous[0]);
1042
+ if (match) match.selected = true;
1043
+ };
1044
+ var sameValues = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
1045
+ var createSetOptions = (state2, helpers) => (name, options, config = {}) => {
1046
+ if (!state2.element || state2.isDestroyed) return state2.api;
1047
+ const selects = helpers.getFieldsByName(name).filter((field) => field instanceof HTMLSelectElement);
1048
+ if (selects.length === 0) return state2.api;
1049
+ const keepValue = config.keepValue ?? true;
1050
+ const changed = [];
1051
+ for (const select of selects) {
1052
+ const previous = selectedValues(select);
1053
+ const { nodes, hasExplicitSelection } = buildNodes(options);
1054
+ select.replaceChildren(...nodes);
1055
+ if (keepValue && !hasExplicitSelection) restoreSelection(select, previous);
1056
+ if (!sameValues(previous, selectedValues(select))) changed.push(select);
1057
+ }
1058
+ helpers.commitOwnMutations(selects);
1059
+ changed[0]?.dispatchEvent(new Event("input", { bubbles: true }));
1060
+ return state2.api;
1061
+ };
1062
+
974
1063
  // src/api/set-value.ts
975
1064
  var createSetValue = (state2, helpers) => (name, value) => {
976
1065
  if (state2.isDestroyed) return state2.api;
@@ -994,28 +1083,46 @@ var createSubscribe = (state2, _helpers) => (listener) => {
994
1083
  };
995
1084
 
996
1085
  // src/api/unwatch.ts
997
- var createUnwatch = (state2, _helpers) => (field, callback) => {
998
- if (!field) {
999
- state2.watchedFields.clear();
1000
- return state2.api;
1001
- }
1002
- if (!callback) {
1003
- state2.watchedFields.delete(field);
1086
+ var sameFields = (left, right) => left.length === right.size && left.every((name) => right.has(name));
1087
+ var createUnwatch = (state2, _helpers) => {
1088
+ function unwatch(field, callback) {
1089
+ if (field === void 0 || field === "") {
1090
+ state2.watchedFields.clear();
1091
+ state2.groupWatchers.clear();
1092
+ return state2.api;
1093
+ }
1094
+ if (Array.isArray(field)) {
1095
+ const names = new Set(field);
1096
+ for (const entry of Array.from(state2.groupWatchers)) {
1097
+ if (!sameFields(entry.fields, names)) continue;
1098
+ if (callback && entry.callback !== callback) continue;
1099
+ state2.groupWatchers.delete(entry);
1100
+ }
1101
+ return state2.api;
1102
+ }
1103
+ if (!callback) {
1104
+ state2.watchedFields.delete(field);
1105
+ return state2.api;
1106
+ }
1107
+ const callbacks = state2.watchedFields.get(field);
1108
+ callbacks?.delete(callback);
1109
+ if (callbacks && callbacks.size === 0) {
1110
+ state2.watchedFields.delete(field);
1111
+ }
1004
1112
  return state2.api;
1005
1113
  }
1006
- const callbacks = state2.watchedFields.get(field);
1007
- callbacks?.delete(callback);
1008
- if (callbacks && callbacks.size === 0) {
1009
- state2.watchedFields.delete(field);
1010
- }
1011
- return state2.api;
1114
+ return unwatch;
1012
1115
  };
1013
1116
 
1014
1117
  // src/api/watch.ts
1015
- var createWatch = (state2, helpers) => (field, callback) => {
1118
+ var createWatch = (state2, helpers) => {
1016
1119
  const observe = createObserve(state2, helpers);
1017
- observe(field, callback);
1018
- return state2.api;
1120
+ function watch(field, callback) {
1121
+ if (Array.isArray(field)) observe(field, callback);
1122
+ else observe(field, callback);
1123
+ return state2.api;
1124
+ }
1125
+ return watch;
1019
1126
  };
1020
1127
 
1021
1128
  // src/core/controller.ts
@@ -1042,6 +1149,7 @@ var createFormController = (target, options = {}) => {
1042
1149
  attributes,
1043
1150
  validators: normalizedOptions.validators ?? {},
1044
1151
  watchedFields: /* @__PURE__ */ new Map(),
1152
+ groupWatchers: /* @__PURE__ */ new Set(),
1045
1153
  subscribers: /* @__PURE__ */ new Set(),
1046
1154
  submitHandlers: /* @__PURE__ */ new Set(),
1047
1155
  fieldCache: /* @__PURE__ */ new Map(),
@@ -1212,7 +1320,7 @@ var createFormController = (target, options = {}) => {
1212
1320
  }
1213
1321
  return affected;
1214
1322
  };
1215
- const emitFieldWatchers = (name) => {
1323
+ const emitSingleWatchers = (name) => {
1216
1324
  if (!state2.element || !state2.api) return;
1217
1325
  const callbacks = state2.watchedFields.get(name);
1218
1326
  if (!callbacks || callbacks.size === 0) return;
@@ -1221,6 +1329,23 @@ var createFormController = (target, options = {}) => {
1221
1329
  const snapshot = state2.api.getState();
1222
1330
  callbacks.forEach((callback) => callback(value, fieldElement, state2.element, snapshot));
1223
1331
  };
1332
+ const emitGroupWatchers = (names) => {
1333
+ if (!state2.element || !state2.api || state2.groupWatchers.size === 0) return;
1334
+ let snapshot;
1335
+ for (const entry of Array.from(state2.groupWatchers)) {
1336
+ const changed = names ? names.find((name) => entry.fields.includes(name)) : null;
1337
+ if (changed === void 0) continue;
1338
+ snapshot ?? (snapshot = state2.api.getState());
1339
+ const values = Object.fromEntries(
1340
+ entry.fields.map((field) => [field, state2.api.getValue(field)])
1341
+ );
1342
+ entry.callback(values, changed, state2.element, snapshot);
1343
+ }
1344
+ };
1345
+ const emitFieldWatchers = (name) => {
1346
+ emitSingleWatchers(name);
1347
+ emitGroupWatchers([name]);
1348
+ };
1224
1349
  const runAutoSubmit = () => {
1225
1350
  state2.autoSubmitTimer = null;
1226
1351
  if (!state2.element || state2.isDestroyed || !state2.autoSubmitEnabled) return;
@@ -1241,8 +1366,20 @@ var createFormController = (target, options = {}) => {
1241
1366
  }
1242
1367
  runAutoSubmit();
1243
1368
  };
1369
+ const selectSnapshots = /* @__PURE__ */ new WeakMap();
1370
+ const readSelectSnapshot = (select) => Array.from(select.selectedOptions, (option) => option.value).join("\0");
1371
+ const snapshotSelect = (select) => {
1372
+ selectSnapshots.set(select, readSelectSnapshot(select));
1373
+ };
1374
+ const snapshotAllSelects = () => {
1375
+ if (!state2.element) return;
1376
+ for (const field of getNamedFields(state2.element)) {
1377
+ if (field instanceof HTMLSelectElement) snapshotSelect(field);
1378
+ }
1379
+ };
1244
1380
  const emitAllFieldWatchers = () => {
1245
- for (const name of Array.from(state2.watchedFields.keys())) emitFieldWatchers(name);
1381
+ for (const name of Array.from(state2.watchedFields.keys())) emitSingleWatchers(name);
1382
+ emitGroupWatchers(null);
1246
1383
  };
1247
1384
  const applyReset = () => {
1248
1385
  if (!state2.element || state2.isDestroyed) return;
@@ -1254,6 +1391,7 @@ var createFormController = (target, options = {}) => {
1254
1391
  state2.validationElementErrors.clear();
1255
1392
  clearAttributes(state2.element, [attributes.error, attributes.filled, "aria-invalid"]);
1256
1393
  syncVisualState();
1394
+ snapshotAllSelects();
1257
1395
  emitAllFieldWatchers();
1258
1396
  notifySubscribers();
1259
1397
  };
@@ -1268,7 +1406,8 @@ var createFormController = (target, options = {}) => {
1268
1406
  validateNames,
1269
1407
  emitFieldWatchers,
1270
1408
  scheduleAutoSubmit,
1271
- applyReset
1409
+ applyReset,
1410
+ commitOwnMutations: (selects) => commitOwnMutations(selects)
1272
1411
  };
1273
1412
  const handleDelegatedEvent = (event) => {
1274
1413
  const target2 = event.target;
@@ -1276,6 +1415,7 @@ var createFormController = (target, options = {}) => {
1276
1415
  const name = target2.name;
1277
1416
  if (!name) return;
1278
1417
  clearFieldCache();
1418
+ if (target2 instanceof HTMLSelectElement) snapshotSelect(target2);
1279
1419
  const canonical = resolveCanonicalForName(state2, name) ?? name;
1280
1420
  const displayName = resolveDisplayNameForName(state2, name);
1281
1421
  if (normalizedOptions.clearManualErrorsOnChange) {
@@ -1286,8 +1426,9 @@ var createFormController = (target, options = {}) => {
1286
1426
  if (state2.isValidated && affectedValidationNames.some((field) => state2.validators[field] !== void 0)) {
1287
1427
  validateNames(affectedValidationNames);
1288
1428
  }
1289
- emitFieldWatchers(canonical);
1290
- if (canonical !== name) emitFieldWatchers(name);
1429
+ emitSingleWatchers(canonical);
1430
+ if (canonical !== name) emitSingleWatchers(name);
1431
+ emitGroupWatchers(canonical !== name ? [canonical, name] : [canonical]);
1291
1432
  notifySubscribers();
1292
1433
  scheduleAutoSubmit();
1293
1434
  };
@@ -1368,18 +1509,62 @@ var createFormController = (target, options = {}) => {
1368
1509
  const cancel = () => clearTimeout(timer);
1369
1510
  state2.cleanups.add(cancel);
1370
1511
  };
1512
+ const touchesFields = (node) => node instanceof Element && (isFieldElement(node) || node.querySelector("input, select, textarea") !== null);
1513
+ const isStructural = (record) => record.type === "attributes" || Array.from(record.addedNodes).some(touchesFields) || Array.from(record.removedNodes).some(touchesFields);
1514
+ const isOptionNode = (node) => node instanceof HTMLOptionElement || node instanceof HTMLOptGroupElement;
1515
+ const optionListOwner = (record) => {
1516
+ if (record.type !== "childList") return null;
1517
+ const touchesOptions = Array.from(record.addedNodes).some(isOptionNode) || Array.from(record.removedNodes).some(isOptionNode);
1518
+ if (!touchesOptions) return null;
1519
+ const target2 = record.target;
1520
+ if (target2 instanceof HTMLSelectElement) return target2;
1521
+ if (target2 instanceof HTMLOptGroupElement && target2.parentElement instanceof HTMLSelectElement) {
1522
+ return target2.parentElement;
1523
+ }
1524
+ return null;
1525
+ };
1526
+ const handleMutations = (records) => {
1527
+ if (state2.isDestroyed) return;
1528
+ const structural = records.some(isStructural);
1529
+ const changedNames = /* @__PURE__ */ new Set();
1530
+ for (const record of records) {
1531
+ const select = optionListOwner(record);
1532
+ if (!select || !select.name) continue;
1533
+ const next = readSelectSnapshot(select);
1534
+ if (selectSnapshots.get(select) !== next) changedNames.add(select.name);
1535
+ selectSnapshots.set(select, next);
1536
+ }
1537
+ if (!structural && changedNames.size === 0) return;
1538
+ clearFieldCache();
1539
+ if (structural) {
1540
+ snapshotAllSelects();
1541
+ if (state2.isValidated) validateNames();
1542
+ else syncVisualState();
1543
+ } else {
1544
+ const affected = /* @__PURE__ */ new Set();
1545
+ for (const name of changedNames) {
1546
+ for (const field of getAffectedValidationNames(name)) affected.add(field);
1547
+ }
1548
+ const names = Array.from(affected);
1549
+ syncVisualState(Array.from(changedNames));
1550
+ if (state2.isValidated && names.some((field) => state2.validators[field] !== void 0)) {
1551
+ validateNames(names);
1552
+ }
1553
+ }
1554
+ for (const name of changedNames) emitFieldWatchers(name);
1555
+ notifySubscribers();
1556
+ };
1557
+ const commitOwnMutations = (selects) => {
1558
+ selects.forEach(snapshotSelect);
1559
+ const pending = state2.mutationObserver?.takeRecords() ?? [];
1560
+ const foreign = pending.filter(
1561
+ (record) => !selects.some((select) => select.contains(record.target))
1562
+ );
1563
+ if (foreign.length > 0) handleMutations(foreign);
1564
+ };
1371
1565
  const startMutationObserver = () => {
1372
1566
  if (!state2.element || typeof MutationObserver === "undefined") return;
1373
- const touchesFields = (node) => node instanceof Element && (isFieldElement(node) || node.querySelector("input, select, textarea") !== null);
1374
- const isRelevant = (record) => record.type === "attributes" || Array.from(record.addedNodes).some(touchesFields) || Array.from(record.removedNodes).some(touchesFields);
1375
- state2.mutationObserver = new MutationObserver((records) => {
1376
- if (state2.isDestroyed || !records.some(isRelevant)) return;
1377
- clearFieldCache();
1378
- if (state2.isValidated) {
1379
- validateNames();
1380
- } else syncVisualState();
1381
- notifySubscribers();
1382
- });
1567
+ state2.mutationObserver = new MutationObserver(handleMutations);
1383
1568
  state2.mutationObserver.observe(state2.element, {
1384
1569
  childList: true,
1385
1570
  subtree: true,
@@ -1408,6 +1593,7 @@ var createFormController = (target, options = {}) => {
1408
1593
  setErrors: createSetErrors(state2, helpers),
1409
1594
  clearErrors: createClearErrors(state2, helpers),
1410
1595
  setValue: createSetValue(state2, helpers),
1596
+ setOptions: createSetOptions(state2, helpers),
1411
1597
  getValue: createGetValue(state2, helpers),
1412
1598
  getField: createGetField(state2, helpers),
1413
1599
  validate: createValidate(state2, helpers),
@@ -1427,6 +1613,7 @@ var createFormController = (target, options = {}) => {
1427
1613
  addListener2(document, "input", handleExternalDelegatedEvent);
1428
1614
  addListener2(state2.element, "submit", handleSubmitEvent);
1429
1615
  addListener2(state2.element, "reset", handleResetEvent);
1616
+ snapshotAllSelects();
1430
1617
  startMutationObserver();
1431
1618
  }
1432
1619
  if (normalizedOptions.autoSubmit) {
@@ -1486,4 +1673,4 @@ export {
1486
1673
  form,
1487
1674
  browser
1488
1675
  };
1489
- //# sourceMappingURL=chunk-23VZAYA3.js.map
1676
+ //# sourceMappingURL=chunk-6MA5TF6R.js.map