@optionfactory/ful 0.102.0 → 0.104.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.
package/dist/ful.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import { ParsedElement, Attributes, Fragments } from '@optionfactory/ftl';
2
+ import TomSelect from 'tom-select';
3
+
1
4
  class Base64 {
2
5
  static encode(arrayBuffer, dialect) {
3
6
  const d = dialect || Base64.URL_SAFE;
@@ -1031,463 +1034,148 @@ const timing = {
1031
1034
  }
1032
1035
  };
1033
1036
 
1034
- class Deferred {
1035
- constructor() {
1036
- this.promise = new Promise((resolve, reject) => {
1037
- this.reject = reject;
1038
- this.resolve = resolve;
1039
- });
1040
- }
1041
- }
1037
+ class Bindings {
1042
1038
 
1043
- class Fragments {
1044
1039
  /**
1045
- *
1046
- * @param {...string} html
1047
- * @returns
1040
+ * @param {{ [x: string]: any; }} obj
1041
+ * @param {string} prefix
1042
+ * @return {{ [x: string]: any; }}
1048
1043
  */
1049
- static fromHtml(...html) {
1050
- const el = document.createElement("template");
1051
- el.innerHTML = html.join("");
1052
- return document.adoptNode(el.content);
1053
- }
1054
- /**
1055
- *
1056
- * @param {DocumentFragment} fragment
1057
- * @returns
1058
- */
1059
- static toHtml(fragment) {
1060
- var el = document.createElement("template");
1061
- el.content.appendChild(fragment);
1062
- return el.innerHTML;
1063
- }
1064
- /**
1065
- *
1066
- * @param {...Node} nodes
1067
- * @returns
1068
- */
1069
- static from(...nodes) {
1070
- const fragment = new DocumentFragment();
1071
- fragment.append(...nodes);
1072
- return fragment;
1073
- }
1074
- /**
1075
- *
1076
- * @param {HTMLElement} el
1077
- * @returns
1078
- */
1079
- static fromChildNodes(el) {
1080
- const fragment = new DocumentFragment();
1081
- fragment.append(...el.childNodes);
1082
- return fragment;
1044
+ static flatten(obj, prefix) {
1045
+ return Object.keys(obj).reduce((acc, k) => {
1046
+ const pre = prefix.length ? prefix + '.' : '';
1047
+ if (typeof obj[k] === 'object' && obj[k] !== null) {
1048
+ Object.assign(acc, Bindings.flatten(obj[k], pre + k));
1049
+ } else {
1050
+ acc[pre + k] = obj[k];
1051
+ }
1052
+ return acc;
1053
+ }, {});
1083
1054
  }
1084
- }
1085
-
1086
- class Attributes {
1087
- static id = 0;
1055
+
1088
1056
  /**
1089
- *
1090
- * @param {string} prefix
1091
- * @returns
1057
+ * @param {any} result
1058
+ * @param {string} path
1059
+ * @param {any} value
1092
1060
  */
1093
- static uid(prefix) {
1094
- return `${prefix}-${++Attributes.id}`;
1061
+ static providePath(result, path, value) {
1062
+ const keys = path.split(".").map((k) => /^[0-9]+$/.test(k) ? +k : k);
1063
+ let current = result ?? {};
1064
+ let previous = null;
1065
+ for (let i = 0; ; ++i) {
1066
+ const ckey = keys[i];
1067
+ const pkey = keys[i - 1];
1068
+ if (Number.isInteger(ckey) && !Array.isArray(current)) {
1069
+ if (previous !== null) {
1070
+ previous[pkey] = current = [];
1071
+ } else {
1072
+ result = current = [];
1073
+ }
1074
+ }
1075
+ if (i === keys.length - 1) {
1076
+ //when value is undefined we only want to define the property if it's not defined
1077
+ current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
1078
+ return result;
1079
+ }
1080
+ if (current[ckey] === undefined) {
1081
+ current[ckey] = {};
1082
+ }
1083
+ previous = current;
1084
+ current = current[ckey];
1085
+ }
1095
1086
  }
1096
1087
  /**
1097
1088
  *
1098
- * @param {HTMLElement} el
1099
- * @param {string} k
1100
- * @param {string} v
1089
+ * @param {Element & {dataset?: any} & {checked?: boolean} & {value?: any}} el
1101
1090
  * @returns
1102
1091
  */
1103
- static defaultValue(el, k, v) {
1104
- if (!el.hasAttribute(k)) {
1105
- el.setAttribute(k, v);
1092
+ static extract(el) {
1093
+ if (el.getAttribute('type') === 'radio') {
1094
+ if (!el.checked) {
1095
+ return undefined;
1096
+ }
1097
+ return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
1106
1098
  }
1107
- return el.getAttribute(k);
1108
- }
1109
- /**
1110
- *
1111
- * @param {string} prefix
1112
- * @param {HTMLElement} from
1113
- * @param {HTMLElement} to
1114
- */
1115
- static forward(prefix, from, to) {
1116
- from.getAttributeNames()
1117
- .filter(a => a.startsWith(prefix))
1118
- .forEach(a => {
1119
- const target = a.substring(prefix.length);
1120
- if (target === 'class') {
1121
- const classes = from.getAttribute(prefix + "class")?.split(" ").filter(a => a.length) ?? [];
1122
- to.classList.add(...classes);
1123
- return;
1124
- }
1125
- // @ts-ignore
1126
- to.setAttribute(target, from.getAttribute(a));
1127
- });
1128
- }
1129
- /**
1130
- *
1131
- * @param {HTMLElement} el
1132
- * @param {string} attr
1133
- * @param {boolean} value
1134
- */
1135
- static toggle(el, attr, value) {
1136
- if (value) {
1137
- el.setAttribute(attr, '');
1138
- } else {
1139
- el.removeAttribute(attr);
1099
+ if (el.getAttribute('type') === 'checkbox') {
1100
+ return el.checked;
1140
1101
  }
1141
- }
1142
- static flip(el, attr) {
1143
- if (el.hasAttribute(attr)) {
1144
- el.removeAttribute(attr);
1145
- } else {
1146
- el.setAttribute(attr, '');
1102
+ if (el.dataset['fulBindType'] === 'boolean') {
1103
+ return !el.value ? null : el.value === 'true';
1147
1104
  }
1148
- }
1149
-
1150
- }
1151
-
1152
- class LightSlots {
1153
- /**
1154
- *
1155
- * @param {HTMLElement} el
1156
- * @returns the slots
1157
- */
1158
- static from(el) {
1159
- /** @type [string, Element][] */
1160
- const namedSlots = Array.from(el.children)
1161
- .filter(el => el.matches('[slot]'))
1162
- .map(el => {
1163
- el.remove();
1164
- const slot = el.getAttribute("slot");
1165
- el.removeAttribute("slot");
1166
- return [slot ?? 'unnamed', el];
1167
- });
1168
- const slots = {};
1169
- slots.default = new DocumentFragment();
1170
- slots.default.append(...el.childNodes);
1171
- for (const [name, el] of namedSlots) {
1172
- if (!(name in slots)) {
1173
- slots[name] = new DocumentFragment();
1174
- }
1175
- slots[name].append(el);
1105
+ if (el.tagName === 'INPUT' || el.tagName === 'SELECT') {
1106
+ return el.value === '' || el.value === undefined ? null : el.value;
1176
1107
  }
1177
- return slots;
1108
+ return el.value;
1178
1109
  }
1179
- }
1180
1110
 
1181
- class Nodes {
1182
- static isParsed(el) {
1183
- for (let c = el; c; c = c.parentNode) {
1184
- if (c.nextSibling) {
1185
- return true;
1111
+ static extractFrom(root, ignoredChildrenSelector){
1112
+ let result = {};
1113
+ for(const el of /** @type {NodeListOf<HTMLElement>} */(root.querySelectorAll('[name]'))){
1114
+ if (el.dataset['fulBindInclude'] === 'never') {
1115
+ continue;
1186
1116
  }
1187
- }
1188
- return false;
1189
- }
1190
- static queryChildren(node, selector) {
1191
- for (const c of node.children) {
1192
- if (c.matches(selector)) {
1193
- return c;
1117
+ if(ignoredChildrenSelector && el.dataset['fulBindInclude'] !== 'always' && el.closest(ignoredChildrenSelector) !== null){
1118
+ continue;
1194
1119
  }
1120
+ result = Bindings.providePath(result, /** @type {string} */(el.getAttribute('name')), Bindings.extract(el));
1195
1121
  }
1196
- return null;
1122
+ return result;
1197
1123
  }
1198
- static queryChildrenAll(node, selector) {
1199
- const r = [];
1200
- for (const c of node.children) {
1201
- if (c.matches(selector)) {
1202
- r.push(c);
1203
- }
1204
- }
1205
- return r;
1206
- }
1207
- }
1208
-
1209
- class TemplatesRegistry {
1210
- #idToFragment = {};
1211
- #idToTemplate = {};
1212
- #ec;
1213
- put(k, fragment) {
1214
- if (this.#ec) {
1215
- // @ts-ignore
1216
- this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, this.#ec);
1124
+
1125
+ /**
1126
+ *
1127
+ * @param {Element & {checked?: boolean} & {value?: any}} el
1128
+ * @returns
1129
+ */
1130
+ static mutate(el, raw) {
1131
+ if (el.getAttribute('type') === 'radio') {
1132
+ el.checked = el.getAttribute('value') === raw;
1217
1133
  return;
1218
1134
  }
1219
- this.#idToFragment[k] = fragment;
1220
- }
1221
- get(k) {
1222
- if (!this.#ec) {
1223
- throw new Error("TemplatesRegistry is not configured");
1224
- }
1225
- const tpl = this.#idToTemplate[k];
1226
- if (!tpl) {
1227
- throw new Error(`missing template: '${k}'`);
1228
- }
1229
- return tpl;
1230
- }
1231
- configure(ec, ...data) {
1232
- this.#ec = ec;
1233
- for (const [k, fragment] of Object.entries(this.#idToFragment)) {
1234
- delete this.#idToFragment[k];
1235
- // @ts-ignore
1236
- this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec, ...data);
1237
- }
1238
- }
1239
- }
1240
-
1241
-
1242
- class ElementsRegistry {
1243
- #templates;
1244
- #tagToclass;
1245
- #configured;
1246
- #id = 0;
1247
- constructor() {
1248
- this.#templates = new TemplatesRegistry();
1249
- this.#tagToclass = {};
1250
- }
1251
- defineTemplate(html) {
1252
- if (html === null || html === undefined) {
1253
- return undefined;
1254
- }
1255
- const name = `unnamed-${++this.#id}`;
1256
- this.#templates.put(name, Fragments.fromHtml(html));
1257
- return name;
1258
- }
1259
- define(tag, klass) {
1260
- if (!this.#configured) {
1261
- this.#tagToclass[tag] = klass;
1262
- return this;
1263
- }
1264
- customElements.define(tag, klass);
1265
- return this;
1266
- }
1267
- configure(ec, ...data) {
1268
- this.#templates.configure(ec, ...data);
1269
- for (const [tag, klass] of Object.entries(this.#tagToclass)) {
1270
- customElements.define(tag, klass);
1271
- delete this.#tagToclass[tag];
1272
- }
1273
- this.#configured = true;
1274
- }
1275
- template(k) {
1276
- if (k === null || k === undefined) {
1277
- return undefined;
1135
+ if (el.getAttribute('type') === 'checkbox') {
1136
+ el.checked = raw;
1137
+ return;
1278
1138
  }
1279
- return this.#templates.get(k);
1139
+ el.value = raw;
1280
1140
  }
1281
- }
1282
-
1283
- const elements = new ElementsRegistry();
1284
1141
 
1285
-
1286
- class UpgradeQueue {
1287
- #q = [];
1288
- constructor() {
1289
- document.addEventListener('DOMContentLoaded', this.dequeue.bind(this));
1290
- }
1291
- enqueue(el) {
1292
- if (!this.#q.length) {
1293
- requestAnimationFrame(this.dequeue.bind(this));
1142
+ static mutateIn(root, values){
1143
+ for (const [flattenedKey, value] of Object.entries(Bindings.flatten(values, ''))) {
1144
+ for(const el of root.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`)){
1145
+ Bindings.mutate(el, value);
1146
+ }
1294
1147
  }
1295
- this.#q.push(el);
1296
- }
1297
- dequeue() {
1298
- this.#q.splice(0).forEach(el => el.upgrade());
1299
1148
  }
1300
- }
1301
1149
 
1302
- const upgradeQueue = new UpgradeQueue();
1303
1150
 
1304
- const mappers = {
1305
- 'string': attr => attr,
1306
- 'number': attr => attr === null ? null : Number(attr),
1307
- 'presence': attr => attr !== null,
1308
- 'state': attr => attr !== null,
1309
- 'bool': attr => attr === 'true',
1310
- 'json': attr => JSON.parse(attr)
1311
- };
1312
-
1313
- const ParsedElement = (conf) => {
1314
- const { observed, template, slots } = conf ?? {};
1315
-
1316
- const attrsAndTypes = (observed ?? []).map(a => {
1317
- const [attr, maybeType] = a.split(":");
1318
- const type = maybeType?.trim() ?? 'string';
1319
- if (!(type in mappers)) {
1320
- throw new Error(`unsupported attribute type: ${type}`);
1321
- }
1322
- return [attr.trim(), type];
1323
- });
1324
-
1325
- const attrsAndMappers = attrsAndTypes.map(([attr, type]) => [attr, mappers[type]]);
1326
-
1327
- const attrToMapper = Object.fromEntries(attrsAndMappers);
1328
-
1329
- const templateId = elements.defineTemplate(template);
1330
-
1331
- const k = class extends HTMLElement {
1332
- static get observedAttributes() {
1333
- return Object.keys(attrToMapper);
1334
- }
1335
- #parsed;
1336
- #initialized;
1337
- #reflecting;
1338
- #internals;
1339
- constructor() {
1340
- super();
1341
- this.#internals = this.attachInternals();
1342
- }
1343
- get initialized() {
1344
- return this.#initialized;
1345
- }
1346
- get internals() {
1347
- return this.#internals;
1348
- }
1349
- connectedCallback() {
1350
- if (this.#parsed) {
1351
- return;
1352
- }
1353
- if (this.ownerDocument.readyState === 'complete' || Nodes.isParsed(this)) {
1354
- upgradeQueue.enqueue(this);
1355
- return;
1356
- }
1357
- this.ownerDocument.addEventListener('DOMContentLoaded', () => {
1358
- observer.disconnect();
1359
- upgradeQueue.enqueue(this);
1360
- });
1361
- const observer = new MutationObserver(() => {
1362
- if (!Nodes.isParsed(this)) {
1363
- return;
1364
- }
1365
- observer.disconnect();
1366
- upgradeQueue.enqueue(this);
1151
+ static errors(root, es, invalidClass){
1152
+ const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1153
+ const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1154
+ root.querySelectorAll(`.${CSS.escape(invalidClass)}`).forEach(el => el.classList.remove(invalidClass));
1155
+ root.querySelectorAll("ful-errors").forEach(el => {
1156
+ el.replaceChildren();
1157
+ el.setAttribute('hidden', '');
1158
+ });
1159
+ fieldErrors.forEach(e => {
1160
+ const name = e.context.replace("[", ".").replace("].", ".");
1161
+ const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
1162
+ root.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(invalidClass));
1163
+ const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
1164
+ root.querySelectorAll(fieldErrorsSelector).forEach(el => {
1165
+ const hel = /** @type HTMLElement} */ (el);
1166
+ hel.innerText = e.reason;
1367
1167
  });
1368
- // @ts-ignore
1369
- observer.observe(this.parentNode, { childList: true, subtree: true });
1370
- }
1371
- attributeChangedCallback(attr, oldValue, newValue) {
1372
- if (!this.#parsed || oldValue === newValue) {
1373
- return;
1374
- }
1375
- if (this.#reflecting) {
1376
- return;
1377
- }
1378
- const mapper = attrToMapper[attr];
1379
- this[attr] = mapper(newValue);
1380
- }
1381
- reflect(fn) {
1382
- this.#reflecting = true;
1383
- try {
1384
- fn();
1385
- } finally {
1386
- this.#reflecting = false;
1387
- }
1388
- }
1389
- async upgrade() {
1390
- if (this.#parsed) {
1391
- return;
1392
- }
1393
- this.#parsed = true;
1394
- // @ts-ignore
1395
- await this.render(elements.template(templateId), slots ? LightSlots.from(this) : undefined);
1396
-
1397
- for (const [attr, mapper] of attrsAndMappers) {
1398
- if (this.hasAttribute(attr)) {
1399
- this[attr] = mapper(this.getAttribute(attr));
1400
- }
1401
- }
1402
- this.#initialized = true;
1403
- }
1404
- };
1405
-
1406
- for (const [attr, type] of attrsAndTypes.filter(([a, t]) => t === 'state')) {
1407
- Object.defineProperty(k.prototype, attr, {
1408
- enumerable: true,
1409
- configurable: true,
1410
- get() {
1411
- return this.internals.states ? this.internals.states.has(`--${attr}`) : this.hasAttribute(attr);
1412
- },
1413
- set(value) {
1414
- this.internals.states?.[value ? 'add' : 'delete'](`--${attr}`);
1415
- this.reflect(() => Attributes.toggle(this, attr, value));
1416
- }
1417
1168
  });
1418
- }
1419
-
1420
- return k;
1421
- };
1422
-
1423
- function flatten(obj, prefix) {
1424
- return Object.keys(obj).reduce((acc, k) => {
1425
- const pre = prefix.length ? prefix + '.' : '';
1426
- if (typeof obj[k] === 'object' && obj[k] !== null) {
1427
- Object.assign(acc, flatten(obj[k], pre + k));
1428
- } else {
1429
- acc[pre + k] = obj[k];
1430
- }
1431
- return acc;
1432
- }, {});
1433
- }
1434
-
1435
- function providePath(result, path, value) {
1436
- const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
1437
- let current = result;
1438
- let previous = null;
1439
- for (let i = 0; ; ++i) {
1440
- const ckey = keys[i];
1441
- const pkey = keys[i - 1];
1442
- if (Number.isInteger(ckey) && !Array.isArray(current)) {
1443
- if (previous !== null) {
1444
- previous[pkey] = current = [];
1445
- } else {
1446
- result = current = [];
1169
+ root.querySelectorAll("ful-errors").forEach(el => {
1170
+ const hel = /** @type HTMLElement} */ (el);
1171
+ hel.innerText = globalErrors.map(e => e.reason).join("\n");
1172
+ if (globalErrors.length !== 0) {
1173
+ el.removeAttribute('hidden');
1447
1174
  }
1448
- }
1449
- if (i === keys.length - 1) {
1450
- //when value is undefined we only want to define the property if it's not defined
1451
- current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
1452
- return result;
1453
- }
1454
- if (current[ckey] === undefined) {
1455
- current[ckey] = {};
1456
- }
1457
- previous = current;
1458
- current = current[ckey];
1459
- }
1460
- }
1175
+ });
1461
1176
 
1462
- function extract(el) {
1463
- if (el.getAttribute('type') === 'radio') {
1464
- if (!el.checked) {
1465
- return undefined;
1466
- }
1467
- return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
1468
- }
1469
- if (el.getAttribute('type') === 'checkbox') {
1470
- return el.checked;
1471
- }
1472
- if (el.dataset['fulBindType'] === 'boolean') {
1473
- return !el.value ? null : el.value === 'true';
1474
- }
1475
- if (el.tagName === 'INPUT' || el.tagName === 'SELECT'){
1476
- return el.value === '' || el.value === undefined ? null : el.value;
1477
- }
1478
- return el.value;
1479
- }
1480
1177
 
1481
- function mutate(el, raw) {
1482
- if (el.getAttribute('type') === 'radio') {
1483
- el.checked = el.getAttribute('value') === raw;
1484
- return;
1485
- }
1486
- if (el.getAttribute('type') === 'checkbox') {
1487
- el.checked = raw;
1488
- return;
1489
1178
  }
1490
- el.value = raw;
1491
1179
  }
1492
1180
 
1493
1181
  class Form extends ParsedElement() {
@@ -1505,12 +1193,12 @@ class Form extends ParsedElement() {
1505
1193
  await this.submitter?.(this.values, this);
1506
1194
  });
1507
1195
  });
1508
- if(this.hasAttribute("clear-invalid-on-change")){
1196
+ if (this.hasAttribute("clear-invalid-on-change")) {
1509
1197
  this.addEventListener('change', evt => {
1510
1198
  const target = /** @type HTMLElement */ (evt.target);
1511
1199
  target?.querySelectorAll(`.${CSS.escape(Form.INVALID_CLASS)}`).forEach(el => {
1512
1200
  el.classList.remove(Form.INVALID_CLASS);
1513
- });
1201
+ });
1514
1202
  });
1515
1203
  }
1516
1204
  this.replaceChildren(form);
@@ -1539,7 +1227,7 @@ class Form extends ParsedElement() {
1539
1227
  this.spinner(true);
1540
1228
  try {
1541
1229
  await this.remoting(fn);
1542
- } catch(e) {
1230
+ } catch (e) {
1543
1231
  this.spinner(false);
1544
1232
  throw e;
1545
1233
  }
@@ -1553,48 +1241,14 @@ class Form extends ParsedElement() {
1553
1241
  }
1554
1242
  }
1555
1243
  set values(vs) {
1556
- for (const [flattenedKey, value] of Object.entries(flatten(vs, ''))) {
1557
- this.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`).forEach(el => mutate(el, value));
1558
- }
1244
+ Bindings.mutateIn(this, vs);
1559
1245
  }
1560
1246
  get values() {
1561
- return Array.from(/** @type {NodeListOf<HTMLElement>} */ (this.querySelectorAll('[name]')))
1562
- .filter(el => {
1563
- if (el.dataset['fulBindInclude'] === 'never') {
1564
- return false;
1565
- }
1566
- return el.dataset['fulBindInclude'] === 'always' || el.closest(Form.IGNORED_CHILDREN_SELECTOR) === null;
1567
- })
1568
- .reduce((result, el) => {
1569
- return providePath(result, el.getAttribute('name'), extract(el));
1570
- }, {});
1247
+ return Bindings.extractFrom(this, Form.IGNORED_CHILDREN_SELECTOR);
1571
1248
  }
1572
1249
  set errors(es) {
1573
- const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1574
- const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1575
- this.querySelectorAll(`.${Form.INVALID_CLASS}`).forEach(el => el.classList.remove(Form.INVALID_CLASS));
1576
- this.querySelectorAll("ful-errors").forEach(el => {
1577
- el.replaceChildren();
1578
- el.setAttribute('hidden', '');
1579
- });
1580
- fieldErrors.forEach(e => {
1581
- const name = e.context.replace("[", ".").replace("].", ".");
1582
- const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
1583
- this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(Form.INVALID_CLASS));
1584
- const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
1585
- this.querySelectorAll(fieldErrorsSelector).forEach(el => {
1586
- const hel = /** @type HTMLElement} */ (el);
1587
- hel.innerText = e.reason;
1588
- });
1589
- });
1590
- this.querySelectorAll("ful-errors").forEach(el => {
1591
- const hel = /** @type HTMLElement} */ (el);
1592
- hel.innerText = globalErrors.map(e => e.reason).join("\n");
1593
- if (globalErrors.length !== 0) {
1594
- el.removeAttribute('hidden');
1595
- }
1596
- });
1597
- if (!this.hasAttribute('scroll-on-error')) {
1250
+ Bindings.errors(this, es, Form.INVALID_CLASS);
1251
+ if (es.length == 0 || !this.hasAttribute('scroll-on-error')) {
1598
1252
  return;
1599
1253
  }
1600
1254
  const ys = Array.from(this.querySelectorAll(`ful-errors:not([hidden]), [ful-validated-field]:has(.${Form.INVALID_CLASS}) ful-field-error`))
@@ -1644,7 +1298,7 @@ const makeInputFragment = (el, template, slots) => {
1644
1298
  Attributes.defaultValue(slots.input, "type", "text");
1645
1299
  Attributes.defaultValue(slots.input, "placeholder", " ");
1646
1300
  const name = el.getAttribute('name');
1647
- return template.render(el, { id, name, slots });
1301
+ return template.withOverlay(el, { id, name, slots }).render();
1648
1302
  };
1649
1303
 
1650
1304
  class Input extends ParsedElement({
@@ -1653,8 +1307,8 @@ class Input extends ParsedElement({
1653
1307
  template: INPUT_TEMPLATE
1654
1308
  }){
1655
1309
  input;
1656
- render(template, slots) {
1657
- const fragment = makeInputFragment(this, template, slots);
1310
+ render({slots}) {
1311
+ const fragment = makeInputFragment(this, this.template(), slots);
1658
1312
  this.replaceChildren(fragment);
1659
1313
  }
1660
1314
  get value() {
@@ -1690,11 +1344,12 @@ class Select extends ParsedElement({
1690
1344
  }) {
1691
1345
  shouldLoad;
1692
1346
  _unwrappedRemoteLoad;
1347
+ ts;
1693
1348
  constructor(tsConfig) {
1694
1349
  super();
1695
1350
  this.tsConfig = tsConfig;
1696
1351
  }
1697
- render(template, slots) {
1352
+ render({slots}) {
1698
1353
  const type = this.getAttribute("type") ?? 'local';
1699
1354
  const remote = type != 'local';
1700
1355
  const loadOnce = this.getAttribute('load') != 'always';
@@ -1741,7 +1396,6 @@ class Select extends ParsedElement({
1741
1396
  }
1742
1397
  callback(data);
1743
1398
  };
1744
- // @ts-ignore
1745
1399
  this.ts = new TomSelect(input, Object.assign(remote ? {
1746
1400
  preload: 'focus',
1747
1401
  load: this._unwrappedRemoteLoad,
@@ -1761,7 +1415,7 @@ class Select extends ParsedElement({
1761
1415
  evt.stopPropagation();
1762
1416
  });
1763
1417
  input.remove();
1764
- template.renderTo(this, { id, tsId, name, input, slots });
1418
+ this.template().withOverlay({ id, tsId, name, input, slots }).renderTo(this);
1765
1419
  }
1766
1420
  #loader;
1767
1421
  set loader(l) {
@@ -1787,7 +1441,7 @@ class Select extends ParsedElement({
1787
1441
  }
1788
1442
 
1789
1443
  class RadioGroup extends ParsedElement({
1790
- observed: ['value', 'disabled:state'],
1444
+ observed: ['value', 'disabled:presence'],
1791
1445
  slots: true,
1792
1446
  template: `
1793
1447
  <fieldset ful-validated-field>
@@ -1812,7 +1466,7 @@ class RadioGroup extends ParsedElement({
1812
1466
  </fieldset>
1813
1467
  `
1814
1468
  }) {
1815
- render(template, slots) {
1469
+ render({slots}) {
1816
1470
  const name = this.getAttribute('name') ?? Attributes.uid('ful-radiogroup');
1817
1471
  const radioEls = Array.from(slots.default.querySelectorAll('ful-radio'));
1818
1472
  const inputsAndLabels = radioEls.map(el => {
@@ -1839,8 +1493,14 @@ class RadioGroup extends ParsedElement({
1839
1493
  });
1840
1494
 
1841
1495
  radioEls.forEach(el => el.remove());
1842
- template.renderTo(this, { name, slots, inputsAndLabels });
1496
+ this.template().withOverlay({ name, slots, inputsAndLabels }).renderTo(this);
1497
+ }
1498
+ get disabled() {
1499
+ return this.hasAttribute('disabled');
1843
1500
  }
1501
+ set disabled(value) {
1502
+ this.reflect(() => Attributes.toggle(this, 'disabled', value));
1503
+ }
1844
1504
  get value() {
1845
1505
  /** @type {HTMLInputElement|null} */
1846
1506
  const checked = this.querySelector('input[type=radio]:checked');
@@ -1848,10 +1508,8 @@ class RadioGroup extends ParsedElement({
1848
1508
  }
1849
1509
  set value(value) {
1850
1510
  if (value === null) {
1851
- /** @type {HTMLInputElement[]} */
1852
1511
  this.querySelectorAll(`input[type=radio]`).forEach(el => {
1853
- // @ts-ignore
1854
- el.checked = false;
1512
+ (/** @type {HTMLInputElement} */(el)).checked = false;
1855
1513
  });
1856
1514
  return;
1857
1515
  }
@@ -1872,10 +1530,10 @@ class Spinner extends ParsedElement({
1872
1530
  </div>
1873
1531
  `
1874
1532
  }) {
1875
- render(template, slots) {
1876
- template.renderTo(this, { slots });
1533
+ render({slots}) {
1534
+ this.template().withOverlay({ slots }).renderTo(this);
1877
1535
  }
1878
1536
  }
1879
1537
 
1880
- export { Attributes, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, Deferred, ElementsRegistry, Failure, Form, Fragments, Hex, HttpClient, HttpClientError, INPUT_TEMPLATE, Input, LightSlots, LocalStorage, MediaType, Nodes, ParsedElement, RadioGroup, Select, SessionStorage, Spinner, TemplatesRegistry, VersionedStorage, elements, makeInputFragment, timing };
1538
+ export { AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, Bindings, Failure, Form, Hex, HttpClient, HttpClientError, INPUT_TEMPLATE, Input, LocalStorage, MediaType, RadioGroup, Select, SessionStorage, Spinner, VersionedStorage, makeInputFragment, timing };
1881
1539
  //# sourceMappingURL=ful.mjs.map