@optionfactory/ful 0.102.0 → 0.103.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;
@@ -1040,454 +1043,148 @@ class Deferred {
1040
1043
  }
1041
1044
  }
1042
1045
 
1043
- class Fragments {
1044
- /**
1045
- *
1046
- * @param {...string} html
1047
- * @returns
1048
- */
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;
1083
- }
1084
- }
1046
+ class Bindings {
1085
1047
 
1086
- class Attributes {
1087
- static id = 0;
1088
1048
  /**
1089
- *
1090
- * @param {string} prefix
1091
- * @returns
1049
+ * @param {{ [x: string]: any; }} obj
1050
+ * @param {string} prefix
1051
+ * @return {{ [x: string]: any; }}
1092
1052
  */
1093
- static uid(prefix) {
1094
- return `${prefix}-${++Attributes.id}`;
1095
- }
1096
- /**
1097
- *
1098
- * @param {HTMLElement} el
1099
- * @param {string} k
1100
- * @param {string} v
1101
- * @returns
1102
- */
1103
- static defaultValue(el, k, v) {
1104
- if (!el.hasAttribute(k)) {
1105
- el.setAttribute(k, v);
1106
- }
1107
- return el.getAttribute(k);
1053
+ static flatten(obj, prefix) {
1054
+ return Object.keys(obj).reduce((acc, k) => {
1055
+ const pre = prefix.length ? prefix + '.' : '';
1056
+ if (typeof obj[k] === 'object' && obj[k] !== null) {
1057
+ Object.assign(acc, Bindings.flatten(obj[k], pre + k));
1058
+ } else {
1059
+ acc[pre + k] = obj[k];
1060
+ }
1061
+ return acc;
1062
+ }, {});
1108
1063
  }
1064
+
1109
1065
  /**
1110
- *
1111
- * @param {string} prefix
1112
- * @param {HTMLElement} from
1113
- * @param {HTMLElement} to
1066
+ * @param {any} result
1067
+ * @param {string} path
1068
+ * @param {any} value
1114
1069
  */
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;
1070
+ static providePath(result, path, value) {
1071
+ const keys = path.split(".").map((k) => /^[0-9]+$/.test(k) ? +k : k);
1072
+ let current = result ?? {};
1073
+ let previous = null;
1074
+ for (let i = 0; ; ++i) {
1075
+ const ckey = keys[i];
1076
+ const pkey = keys[i - 1];
1077
+ if (Number.isInteger(ckey) && !Array.isArray(current)) {
1078
+ if (previous !== null) {
1079
+ previous[pkey] = current = [];
1080
+ } else {
1081
+ result = current = [];
1124
1082
  }
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);
1140
- }
1141
- }
1142
- static flip(el, attr) {
1143
- if (el.hasAttribute(attr)) {
1144
- el.removeAttribute(attr);
1145
- } else {
1146
- el.setAttribute(attr, '');
1147
- }
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
1083
  }
1175
- slots[name].append(el);
1176
- }
1177
- return slots;
1178
- }
1179
- }
1180
-
1181
- class Nodes {
1182
- static isParsed(el) {
1183
- for (let c = el; c; c = c.parentNode) {
1184
- if (c.nextSibling) {
1185
- return true;
1084
+ if (i === keys.length - 1) {
1085
+ //when value is undefined we only want to define the property if it's not defined
1086
+ current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
1087
+ return result;
1186
1088
  }
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;
1089
+ if (current[ckey] === undefined) {
1090
+ current[ckey] = {};
1194
1091
  }
1092
+ previous = current;
1093
+ current = current[ckey];
1195
1094
  }
1196
- return null;
1197
1095
  }
1198
- static queryChildrenAll(node, selector) {
1199
- const r = [];
1200
- for (const c of node.children) {
1201
- if (c.matches(selector)) {
1202
- r.push(c);
1096
+ /**
1097
+ *
1098
+ * @param {Element & {dataset?: any} & {checked?: boolean} & {value?: any}} el
1099
+ * @returns
1100
+ */
1101
+ static extract(el) {
1102
+ if (el.getAttribute('type') === 'radio') {
1103
+ if (!el.checked) {
1104
+ return undefined;
1203
1105
  }
1106
+ return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
1204
1107
  }
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);
1217
- return;
1218
- }
1219
- this.#idToFragment[k] = fragment;
1220
- }
1221
- get(k) {
1222
- if (!this.#ec) {
1223
- throw new Error("TemplatesRegistry is not configured");
1108
+ if (el.getAttribute('type') === 'checkbox') {
1109
+ return el.checked;
1224
1110
  }
1225
- const tpl = this.#idToTemplate[k];
1226
- if (!tpl) {
1227
- throw new Error(`missing template: '${k}'`);
1111
+ if (el.dataset['fulBindType'] === 'boolean') {
1112
+ return !el.value ? null : el.value === 'true';
1228
1113
  }
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);
1114
+ if (el.tagName === 'INPUT' || el.tagName === 'SELECT') {
1115
+ return el.value === '' || el.value === undefined ? null : el.value;
1237
1116
  }
1117
+ return el.value;
1238
1118
  }
1239
- }
1240
-
1241
1119
 
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;
1120
+ static extractFrom(root, ignoredChildrenSelector){
1121
+ let result = {};
1122
+ for(const el of /** @type {NodeListOf<HTMLElement>} */(root.querySelectorAll('[name]'))){
1123
+ if (el.dataset['fulBindInclude'] === 'never') {
1124
+ continue;
1125
+ }
1126
+ if(ignoredChildrenSelector && el.dataset['fulBindInclude'] !== 'always' && el.closest(ignoredChildrenSelector) !== null){
1127
+ continue;
1128
+ }
1129
+ result = Bindings.providePath(result, /** @type {string} */(el.getAttribute('name')), Bindings.extract(el));
1263
1130
  }
1264
- customElements.define(tag, klass);
1265
- return this;
1131
+ return result;
1266
1132
  }
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];
1133
+
1134
+ /**
1135
+ *
1136
+ * @param {Element & {checked?: boolean} & {value?: any}} el
1137
+ * @returns
1138
+ */
1139
+ static mutate(el, raw) {
1140
+ if (el.getAttribute('type') === 'radio') {
1141
+ el.checked = el.getAttribute('value') === raw;
1142
+ return;
1272
1143
  }
1273
- this.#configured = true;
1274
- }
1275
- template(k) {
1276
- if (k === null || k === undefined) {
1277
- return undefined;
1144
+ if (el.getAttribute('type') === 'checkbox') {
1145
+ el.checked = raw;
1146
+ return;
1278
1147
  }
1279
- return this.#templates.get(k);
1148
+ el.value = raw;
1280
1149
  }
1281
- }
1282
1150
 
1283
- const elements = new ElementsRegistry();
1284
-
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));
1151
+ static mutateIn(root, values){
1152
+ for (const [flattenedKey, value] of Object.entries(Bindings.flatten(values, ''))) {
1153
+ for(const el of root.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`)){
1154
+ Bindings.mutate(el, value);
1155
+ }
1294
1156
  }
1295
- this.#q.push(el);
1296
- }
1297
- dequeue() {
1298
- this.#q.splice(0).forEach(el => el.upgrade());
1299
1157
  }
1300
- }
1301
-
1302
- const upgradeQueue = new UpgradeQueue();
1303
1158
 
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
1159
 
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);
1160
+ static errors(root, es, invalidClass){
1161
+ const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1162
+ const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1163
+ root.querySelectorAll(`.${CSS.escape(invalidClass)}`).forEach(el => el.classList.remove(invalidClass));
1164
+ root.querySelectorAll("ful-errors").forEach(el => {
1165
+ el.replaceChildren();
1166
+ el.setAttribute('hidden', '');
1167
+ });
1168
+ fieldErrors.forEach(e => {
1169
+ const name = e.context.replace("[", ".").replace("].", ".");
1170
+ const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
1171
+ root.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(invalidClass));
1172
+ const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
1173
+ root.querySelectorAll(fieldErrorsSelector).forEach(el => {
1174
+ const hel = /** @type HTMLElement} */ (el);
1175
+ hel.innerText = e.reason;
1367
1176
  });
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
1177
  });
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 = [];
1178
+ root.querySelectorAll("ful-errors").forEach(el => {
1179
+ const hel = /** @type HTMLElement} */ (el);
1180
+ hel.innerText = globalErrors.map(e => e.reason).join("\n");
1181
+ if (globalErrors.length !== 0) {
1182
+ el.removeAttribute('hidden');
1447
1183
  }
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
- }
1184
+ });
1461
1185
 
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
1186
 
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
1187
  }
1490
- el.value = raw;
1491
1188
  }
1492
1189
 
1493
1190
  class Form extends ParsedElement() {
@@ -1505,12 +1202,12 @@ class Form extends ParsedElement() {
1505
1202
  await this.submitter?.(this.values, this);
1506
1203
  });
1507
1204
  });
1508
- if(this.hasAttribute("clear-invalid-on-change")){
1205
+ if (this.hasAttribute("clear-invalid-on-change")) {
1509
1206
  this.addEventListener('change', evt => {
1510
1207
  const target = /** @type HTMLElement */ (evt.target);
1511
1208
  target?.querySelectorAll(`.${CSS.escape(Form.INVALID_CLASS)}`).forEach(el => {
1512
1209
  el.classList.remove(Form.INVALID_CLASS);
1513
- });
1210
+ });
1514
1211
  });
1515
1212
  }
1516
1213
  this.replaceChildren(form);
@@ -1539,7 +1236,7 @@ class Form extends ParsedElement() {
1539
1236
  this.spinner(true);
1540
1237
  try {
1541
1238
  await this.remoting(fn);
1542
- } catch(e) {
1239
+ } catch (e) {
1543
1240
  this.spinner(false);
1544
1241
  throw e;
1545
1242
  }
@@ -1553,48 +1250,14 @@ class Form extends ParsedElement() {
1553
1250
  }
1554
1251
  }
1555
1252
  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
- }
1253
+ Bindings.mutateIn(this, vs);
1559
1254
  }
1560
1255
  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
- }, {});
1256
+ return Bindings.extractFrom(this, Form.IGNORED_CHILDREN_SELECTOR);
1571
1257
  }
1572
1258
  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')) {
1259
+ Bindings.errors(this, es, Form.INVALID_CLASS);
1260
+ if (es.length == 0 || !this.hasAttribute('scroll-on-error')) {
1598
1261
  return;
1599
1262
  }
1600
1263
  const ys = Array.from(this.querySelectorAll(`ful-errors:not([hidden]), [ful-validated-field]:has(.${Form.INVALID_CLASS}) ful-field-error`))
@@ -1644,7 +1307,7 @@ const makeInputFragment = (el, template, slots) => {
1644
1307
  Attributes.defaultValue(slots.input, "type", "text");
1645
1308
  Attributes.defaultValue(slots.input, "placeholder", " ");
1646
1309
  const name = el.getAttribute('name');
1647
- return template.render(el, { id, name, slots });
1310
+ return template.withOverlay(el, { id, name, slots }).render();
1648
1311
  };
1649
1312
 
1650
1313
  class Input extends ParsedElement({
@@ -1653,8 +1316,8 @@ class Input extends ParsedElement({
1653
1316
  template: INPUT_TEMPLATE
1654
1317
  }){
1655
1318
  input;
1656
- render(template, slots) {
1657
- const fragment = makeInputFragment(this, template, slots);
1319
+ render({slots}) {
1320
+ const fragment = makeInputFragment(this, this.template(), slots);
1658
1321
  this.replaceChildren(fragment);
1659
1322
  }
1660
1323
  get value() {
@@ -1690,11 +1353,12 @@ class Select extends ParsedElement({
1690
1353
  }) {
1691
1354
  shouldLoad;
1692
1355
  _unwrappedRemoteLoad;
1356
+ ts;
1693
1357
  constructor(tsConfig) {
1694
1358
  super();
1695
1359
  this.tsConfig = tsConfig;
1696
1360
  }
1697
- render(template, slots) {
1361
+ render({slots}) {
1698
1362
  const type = this.getAttribute("type") ?? 'local';
1699
1363
  const remote = type != 'local';
1700
1364
  const loadOnce = this.getAttribute('load') != 'always';
@@ -1741,7 +1405,6 @@ class Select extends ParsedElement({
1741
1405
  }
1742
1406
  callback(data);
1743
1407
  };
1744
- // @ts-ignore
1745
1408
  this.ts = new TomSelect(input, Object.assign(remote ? {
1746
1409
  preload: 'focus',
1747
1410
  load: this._unwrappedRemoteLoad,
@@ -1761,7 +1424,7 @@ class Select extends ParsedElement({
1761
1424
  evt.stopPropagation();
1762
1425
  });
1763
1426
  input.remove();
1764
- template.renderTo(this, { id, tsId, name, input, slots });
1427
+ this.template().withOverlay({ id, tsId, name, input, slots }).renderTo(this);
1765
1428
  }
1766
1429
  #loader;
1767
1430
  set loader(l) {
@@ -1787,7 +1450,7 @@ class Select extends ParsedElement({
1787
1450
  }
1788
1451
 
1789
1452
  class RadioGroup extends ParsedElement({
1790
- observed: ['value', 'disabled:state'],
1453
+ observed: ['value', 'disabled:presence'],
1791
1454
  slots: true,
1792
1455
  template: `
1793
1456
  <fieldset ful-validated-field>
@@ -1812,7 +1475,7 @@ class RadioGroup extends ParsedElement({
1812
1475
  </fieldset>
1813
1476
  `
1814
1477
  }) {
1815
- render(template, slots) {
1478
+ render({slots}) {
1816
1479
  const name = this.getAttribute('name') ?? Attributes.uid('ful-radiogroup');
1817
1480
  const radioEls = Array.from(slots.default.querySelectorAll('ful-radio'));
1818
1481
  const inputsAndLabels = radioEls.map(el => {
@@ -1839,8 +1502,14 @@ class RadioGroup extends ParsedElement({
1839
1502
  });
1840
1503
 
1841
1504
  radioEls.forEach(el => el.remove());
1842
- template.renderTo(this, { name, slots, inputsAndLabels });
1505
+ this.template().withOverlay({ name, slots, inputsAndLabels }).renderTo(this);
1506
+ }
1507
+ get disabled() {
1508
+ return this.hasAttribute('disabled');
1843
1509
  }
1510
+ set disabled(value) {
1511
+ this.reflect(() => Attributes.toggle(this, 'disabled', value));
1512
+ }
1844
1513
  get value() {
1845
1514
  /** @type {HTMLInputElement|null} */
1846
1515
  const checked = this.querySelector('input[type=radio]:checked');
@@ -1848,10 +1517,8 @@ class RadioGroup extends ParsedElement({
1848
1517
  }
1849
1518
  set value(value) {
1850
1519
  if (value === null) {
1851
- /** @type {HTMLInputElement[]} */
1852
1520
  this.querySelectorAll(`input[type=radio]`).forEach(el => {
1853
- // @ts-ignore
1854
- el.checked = false;
1521
+ (/** @type {HTMLInputElement} */(el)).checked = false;
1855
1522
  });
1856
1523
  return;
1857
1524
  }
@@ -1872,10 +1539,10 @@ class Spinner extends ParsedElement({
1872
1539
  </div>
1873
1540
  `
1874
1541
  }) {
1875
- render(template, slots) {
1876
- template.renderTo(this, { slots });
1542
+ render({slots}) {
1543
+ this.template().withOverlay({ slots }).renderTo(this);
1877
1544
  }
1878
1545
  }
1879
1546
 
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 };
1547
+ export { AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, Bindings, Deferred, Failure, Form, Hex, HttpClient, HttpClientError, INPUT_TEMPLATE, Input, LocalStorage, MediaType, RadioGroup, Select, SessionStorage, Spinner, VersionedStorage, makeInputFragment, timing };
1881
1548
  //# sourceMappingURL=ful.mjs.map