formeo 5.2.0 → 5.2.2

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,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  formeo - https://formeo.io
4
- Version: 5.1.3
4
+ Version: 5.2.1
5
5
  Author: Draggable https://draggable.io
6
6
  */
7
7
 
@@ -6064,7 +6064,7 @@ Author: Draggable https://draggable.io
6064
6064
  var name$1, version$2, type, main, module$1, unpkg, exports$1, files, homepage, repository, author, contributors, bugs, description, keywords, ignore, config, scripts, devDependencies, dependencies, release, commitlint, package_default;
6065
6065
  var init_package = __esmMin((() => {
6066
6066
  name$1 = "formeo";
6067
- version$2 = "5.1.3";
6067
+ version$2 = "5.2.1";
6068
6068
  type = "module";
6069
6069
  main = "dist/formeo.cjs.js";
6070
6070
  module$1 = "dist/formeo.es.js";
@@ -6126,7 +6126,7 @@ Author: Draggable https://draggable.io
6126
6126
  "postbuild:demo": "node --no-warnings tools/copy-assets.mjs",
6127
6127
  "build:demo:watch": "vite build --mode demo --watch",
6128
6128
  "build:icons": "node ./tools/generate-sprite",
6129
- "lint": "biome check ./src && node tools/check-color-literals.mjs",
6129
+ "lint": "biome check ./src && node tools/check-color-literals.mjs && node tools/check-doc-links.mjs",
6130
6130
  "lint:fix": "biome check --write ./src",
6131
6131
  "format": "biome format --write .",
6132
6132
  "test": "node --loader=./tools/svg-loader.mjs --import=./tools/__mocks__/sprite-init.mjs --experimental-test-snapshots --require ./tools/test-setup.cjs --test --no-warnings src/**/*.test.{js,mjs}",
@@ -6168,8 +6168,7 @@ Author: Draggable https://draggable.io
6168
6168
  "vite-plugin-banner": "^0.8.0",
6169
6169
  "vite-plugin-compression": "^0.5.1",
6170
6170
  "vite-plugin-html": "^3.2.2",
6171
- "zod": "^4.4.3",
6172
- "zod-to-json-schema": "^3.23.5"
6171
+ "zod": "^4.4.3"
6173
6172
  };
6174
6173
  dependencies = {
6175
6174
  "@draggable/formeo-languages": "^3.4.1",
@@ -12090,7 +12089,7 @@ Author: Draggable https://draggable.io
12090
12089
  }));
12091
12090
  //#endregion
12092
12091
  //#region src/lib/js/common/dom.js
12093
- var iconFontTemplates, inputTags, stripOn, useCaptureEvts, defaultActionHandler, getName, DOM, dom;
12092
+ var iconFontTemplates, inputTags, REQUIRED_GROUP_ATTR, OPTION_INPUT_ATTRS, GROUP_CONSUMED_ATTRS, groupWrapperAttrs, stripOn, useCaptureEvts, defaultActionHandler, getName, DOM, dom;
12094
12093
  var init_dom = __esmMin((() => {
12095
12094
  init_components();
12096
12095
  init_constants();
@@ -12112,6 +12111,18 @@ Author: Draggable https://draggable.io
12112
12111
  "textarea",
12113
12112
  "select"
12114
12113
  ]);
12114
+ REQUIRED_GROUP_ATTR = "formeo-required-group";
12115
+ OPTION_INPUT_ATTRS = ["disabled", "form"];
12116
+ GROUP_CONSUMED_ATTRS = new Set([
12117
+ "type",
12118
+ "id",
12119
+ "name",
12120
+ "className",
12121
+ "value",
12122
+ "required",
12123
+ ...OPTION_INPUT_ATTRS
12124
+ ]);
12125
+ groupWrapperAttrs = (attrs = {}) => Object.fromEntries(Object.entries(attrs).filter(([key]) => !GROUP_CONSUMED_ATTRS.has(key)));
12115
12126
  stripOn = (str) => str.replace(/^on([A-Z])/, (_, l) => l.toLowerCase());
12116
12127
  useCaptureEvts = new Set(["focus", "blur"]);
12117
12128
  defaultActionHandler = (event) => {
@@ -12235,9 +12246,18 @@ Author: Draggable https://draggable.io
12235
12246
  helpers.forEach(processedOptions, (option) => {
12236
12247
  wrap.children.push(_this.create(option, isPreview));
12237
12248
  });
12238
- if (elem.attrs.className) wrap.className = elem.attrs.className;
12249
+ const groupAttrs = elem.attrs || {};
12250
+ if (groupAttrs.className) wrap.className = groupAttrs.className;
12239
12251
  wrap.id = elem.id;
12240
- wrap.config = { ...elem.config };
12252
+ wrap.attrs = groupWrapperAttrs(groupAttrs);
12253
+ wrap.config = {
12254
+ ...elem.config,
12255
+ required: Boolean(groupAttrs.required)
12256
+ };
12257
+ if (!isPreview && groupAttrs.type === "checkbox" && groupAttrs.required) {
12258
+ wrap.attrs[`data-${REQUIRED_GROUP_ATTR}`] = "true";
12259
+ wrap.action = { change: ({ currentTarget }) => this.syncCheckboxGroupRequired(currentTarget) };
12260
+ }
12241
12261
  return this.create(wrap, isPreview);
12242
12262
  }
12243
12263
  processed.push("options");
@@ -12440,16 +12460,20 @@ Author: Draggable https://draggable.io
12440
12460
  const { action, attrs = {} } = elem;
12441
12461
  const fieldType = attrs.type || elem.tag;
12442
12462
  const id = attrs.id || elem.id;
12463
+ const name = !isPreview && attrs.name || id;
12464
+ const sharedInputAttrs = Object.fromEntries(OPTION_INPUT_ATTRS.filter((key) => key in attrs).map((key) => [key, attrs[key]]));
12465
+ if (attrs.required) sharedInputAttrs.required = fieldType !== "checkbox" || !options.some(({ selected, checked }) => selected || checked);
12443
12466
  const optionMap = (option, i) => {
12444
12467
  const { label, value, ...rest } = option;
12445
12468
  const defaultInput = () => {
12446
12469
  const input = {
12447
12470
  tag: "input",
12448
12471
  attrs: {
12449
- name: id,
12472
+ name,
12450
12473
  type: fieldType,
12451
12474
  value: value || "",
12452
12475
  id: `${id}-${i}`,
12476
+ ...sharedInputAttrs,
12453
12477
  ...rest
12454
12478
  },
12455
12479
  action
@@ -12551,6 +12575,18 @@ Author: Draggable https://draggable.io
12551
12575
  const labelAfter = helpers.get(elem, "config.labelAfter");
12552
12576
  return labelAfter === void 0 ? type === "checkbox" || type === "radio" : labelAfter;
12553
12577
  }
12578
+ /**
12579
+ * A required checkbox group needs at least one checked box, not every box.
12580
+ * Every box stays `required` while none is checked; once one is checked none is.
12581
+ * Boxes inside a hidden container are never required.
12582
+ * @param {Element} groupElem wrapper holding the group's checkboxes
12583
+ */
12584
+ syncCheckboxGroupRequired(groupElem) {
12585
+ const boxes = Array.from(groupElem.querySelectorAll("input[type=\"checkbox\"]"));
12586
+ const isHidden = Boolean(groupElem.closest("[hidden]"));
12587
+ const noneChecked = !boxes.some((box) => box.checked);
12588
+ for (const box of boxes) box.required = !isHidden && noneChecked;
12589
+ }
12554
12590
  requiredMark = () => ({
12555
12591
  tag: "span",
12556
12592
  className: "text-error",
@@ -12574,7 +12610,7 @@ Author: Draggable https://draggable.io
12574
12610
  * @return {Object} config object
12575
12611
  */
12576
12612
  label(elem, fMap) {
12577
- const required = helpers.get(elem, "attrs.required");
12613
+ const required = helpers.get(elem, "attrs.required") || helpers.get(elem, "config.required");
12578
12614
  let { config: { label: labelText = "", helpText = "", tooltip = null } } = elem;
12579
12615
  const { id: elemId, attrs } = elem;
12580
12616
  if (typeof labelText === "function") labelText = labelText();
@@ -18169,12 +18205,23 @@ Author: Draggable https://draggable.io
18169
18205
  acc[key] = assignmentHandlers[key];
18170
18206
  return acc;
18171
18207
  }, {});
18208
+ /**
18209
+ * Setting `checked` fires no `change`, so a required checkbox group the box belongs to is
18210
+ * re-synced directly. A synthetic `change` could re-trigger conditions.
18211
+ * @param {Element} elem checkbox or radio input
18212
+ */
18213
+ var syncRequiredGroupOf = (elem) => {
18214
+ const group = elem.closest?.(REQUIRED_GROUP_SELECTOR);
18215
+ if (group) dom.syncCheckboxGroupRequired(group);
18216
+ };
18172
18217
  var targetPropertyMap = {
18173
18218
  isChecked: (elem) => {
18174
18219
  elem.checked = true;
18220
+ syncRequiredGroupOf(elem);
18175
18221
  },
18176
18222
  isNotChecked: (elem) => {
18177
18223
  elem.checked = false;
18224
+ syncRequiredGroupOf(elem);
18178
18225
  },
18179
18226
  value: (elem, { assignment, ...rest }) => {
18180
18227
  const assignmentAction = assignmentMap[assignment]?.(elem, rest);
@@ -18183,15 +18230,60 @@ Author: Draggable https://draggable.io
18183
18230
  return assignmentAction;
18184
18231
  },
18185
18232
  isNotVisible: (elem) => {
18186
- if (elem?._required === void 0) elem._required = elem.required;
18187
18233
  elem.parentElement.setAttribute("hidden", true);
18188
- elem.required = false;
18234
+ suspendRequired(elem);
18189
18235
  },
18190
18236
  isVisible: (elem) => {
18191
18237
  elem.parentElement.removeAttribute("hidden");
18192
- elem.required = elem._required;
18238
+ restoreRequired(elem);
18239
+ }
18240
+ };
18241
+ var FORM_CONTROL_SELECTOR = "input, select, textarea";
18242
+ var REQUIRED_GROUP_SELECTOR = `[data-${REQUIRED_GROUP_ATTR}]`;
18243
+ /**
18244
+ * The element itself when it matches, plus every descendant that does
18245
+ * @param {Element} elem
18246
+ * @param {String} selector
18247
+ * @return {Array<Element>}
18248
+ */
18249
+ var selfAndDescendants = (elem, selector) => [...elem.matches(selector) ? [elem] : [], ...elem.querySelectorAll(selector)];
18250
+ /**
18251
+ * A hidden control can't be filled in, so it must not be required. Remembers each control's
18252
+ * `required` so that restoreRequired can put it back. Works for a field, a group or a whole row.
18253
+ * @param {Element} elem condition target
18254
+ */
18255
+ var suspendRequired = (elem) => {
18256
+ for (const control of selfAndDescendants(elem, FORM_CONTROL_SELECTOR)) {
18257
+ if (control._required === void 0) control._required = control.required;
18258
+ control.required = false;
18259
+ }
18260
+ };
18261
+ /**
18262
+ * Undoes suspendRequired. Controls that were never suspended are left alone, and so are controls
18263
+ * still inside a hidden container: they keep their saved `required` until that container is shown.
18264
+ * @param {Element} elem condition target
18265
+ */
18266
+ var restoreRequired = (elem) => {
18267
+ for (const control of selfAndDescendants(elem, FORM_CONTROL_SELECTOR)) if (control._required !== void 0 && !control.closest("[hidden]")) {
18268
+ control.required = control._required;
18269
+ delete control._required;
18193
18270
  }
18271
+ for (const group of selfAndDescendants(elem, REQUIRED_GROUP_SELECTOR)) dom.syncCheckboxGroupRequired(group);
18194
18272
  };
18273
+ var LOGICAL_AND = new Set(["&&", "and"]);
18274
+ /**
18275
+ * Splits a condition's if-clauses into OR-groups of AND-ed clauses. A clause's `logical`
18276
+ * ('&&' / '||', or 'and' / 'or') joins it to the clause before it, and '&&' binds tighter
18277
+ * than '||' as in JavaScript. The first clause's `logical` is ignored, and a missing
18278
+ * `logical` means OR, which is how every clause behaved before AND was supported.
18279
+ * @param {Array<Object>} ifConditions
18280
+ * @return {Array<Array<Object>>} e.g. A || B && C -> [[A], [B, C]]
18281
+ */
18282
+ var groupIfConditions = (ifConditions = []) => ifConditions.reduce((groups, clause, index) => {
18283
+ if (index > 0 && LOGICAL_AND.has(clause.logical)) groups[groups.length - 1].push(clause);
18284
+ else groups.push([clause]);
18285
+ return groups;
18286
+ }, []);
18195
18287
  //#endregion
18196
18288
  //#region src/lib/js/renderer/index.js
18197
18289
  init_dom();
@@ -18256,20 +18348,31 @@ Author: Draggable https://draggable.io
18256
18348
  const fieldData = {
18257
18349
  key,
18258
18350
  value,
18259
- label: this.components[baseId(key)]?.config?.label || ""
18351
+ label: this.componentByName(key)?.config?.label || ""
18260
18352
  };
18261
18353
  userFormData.push(fieldData);
18262
18354
  }
18263
18355
  return userFormData;
18264
18356
  }
18357
+ /**
18358
+ * Finds the component data behind a submitted field name
18359
+ * @param {String} name
18360
+ * @return {Object|undefined}
18361
+ */
18362
+ componentByName(name) {
18363
+ return this.components[baseId(name)] || Object.values(this.components).find((component) => component.attrs?.name === name);
18364
+ }
18265
18365
  set userData(data = {}) {
18266
18366
  const form = this.container.querySelector("form");
18267
18367
  for (const key of Object.keys(data)) {
18268
18368
  const fields = form.elements[key];
18269
- if (fields.length && fields[0].type === "checkbox") {
18369
+ const checkables = checkableInputs(fields);
18370
+ if (checkables?.[0].type === "checkbox") {
18270
18371
  const values = Array.isArray(data[key]) ? data[key] : [data[key]];
18271
- for (const field of fields) field.checked = values.includes(field.value);
18272
- } else if (fields.length && fields[0].type === "radio") for (const field of fields) field.checked = field.value === data[key];
18372
+ for (const field of checkables) field.checked = values.includes(field.value);
18373
+ const group = checkables[0].closest(`[data-${REQUIRED_GROUP_ATTR}]`);
18374
+ if (group) dom.syncCheckboxGroupRequired(group);
18375
+ } else if (checkables?.[0].type === "radio") for (const field of checkables) field.checked = field.value === data[key];
18273
18376
  else if (fields.type) fields.value = data[key];
18274
18377
  }
18275
18378
  }
@@ -18295,9 +18398,20 @@ Author: Draggable https://draggable.io
18295
18398
  children: this.processedData
18296
18399
  };
18297
18400
  this.renderedForm = dom.render(config);
18401
+ this.renderedForm.addEventListener("reset", this.syncRequiredGroupsAfterReset);
18298
18402
  this.applyConditions();
18299
18403
  return this.renderedForm;
18300
18404
  }
18405
+ /**
18406
+ * A reset changes checkedness without firing `change`, so required checkbox groups are re-synced.
18407
+ * The `reset` event fires before the controls revert, hence the deferral.
18408
+ * @param {Event} evt the form's reset event
18409
+ */
18410
+ syncRequiredGroupsAfterReset = ({ currentTarget: form }) => {
18411
+ setTimeout(() => {
18412
+ for (const group of form.querySelectorAll(`[data-${REQUIRED_GROUP_ATTR}]`)) dom.syncCheckboxGroupRequired(group);
18413
+ }, 0);
18414
+ };
18301
18415
  get html() {
18302
18416
  return (this.renderedForm || this.getRenderedForm()).outerHTML;
18303
18417
  }
@@ -18372,7 +18486,8 @@ Author: Draggable https://draggable.io
18372
18486
  ...attrs,
18373
18487
  "data-clone-of": id
18374
18488
  };
18375
- if (rest.tag === "input") updatedAttrs.name = getName(this.components[componentId]);
18489
+ if (rest.options && ["checkbox", "radio"].includes(attrs.type)) delete updatedAttrs.name;
18490
+ else if (rest.tag === "input") updatedAttrs.name = getName(this.components[componentId]);
18376
18491
  return {
18377
18492
  ...rest,
18378
18493
  id: "f-" + uuid(id),
@@ -18417,46 +18532,57 @@ Author: Draggable https://draggable.io
18417
18532
  });
18418
18533
  }
18419
18534
  /**
18420
- * Evaulate and execute conditions for fields by creating listeners for input and changes
18421
- * @return {Array} flattened array of conditions
18535
+ * Wires every condition of every rendered component: evaluates it once on render and again
18536
+ * whenever a component one of its if-clauses reads from changes.
18422
18537
  */
18423
- handleComponentCondition = (component, ifRest, thenConditions) => {
18424
- if (!component) return;
18425
- if (isNodeCollection(component)) {
18426
- for (const elem of component) this.handleComponentCondition(elem, ifRest, thenConditions);
18427
- return;
18428
- }
18429
- const listenerEvent = LISTEN_TYPE_MAP(component);
18430
- if (listenerEvent) component.addEventListener(listenerEvent, (evt) => {
18431
- if (this.evaluateCondition(ifRest, evt)) for (const thenCondition of thenConditions) this.execResult(thenCondition, evt);
18432
- }, false);
18433
- const fakeEvt = { target: component };
18434
- if (this.evaluateCondition(ifRest, fakeEvt)) for (const thenCondition of thenConditions) this.execResult(thenCondition, fakeEvt);
18435
- };
18436
18538
  applyConditions = () => {
18437
18539
  for (const { conditions } of Object.values(this.components)) {
18438
18540
  if (!conditions) continue;
18439
- for (const condition of conditions) {
18440
- const { if: ifConditions = [], then: thenConditions = [] } = condition;
18441
- for (const ifCondition of ifConditions) try {
18442
- this.applyCondition(ifCondition, thenConditions);
18443
- } catch (err) {
18444
- console.error("formeo: condition skipped", ifCondition, err);
18445
- }
18541
+ for (const condition of conditions) try {
18542
+ this.applyCondition(condition);
18543
+ } catch (err) {
18544
+ console.error("formeo: condition skipped", condition, err);
18446
18545
  }
18447
18546
  }
18448
18547
  };
18449
- applyCondition = (ifCondition, thenConditions) => {
18450
- for (const address of [ifCondition.source, ifCondition.target]) {
18451
- if (!isAddress(address)) continue;
18548
+ applyCondition = ({ if: ifConditions = [], then: thenConditions = [] }) => {
18549
+ const clauseGroups = groupIfConditions(ifConditions);
18550
+ let running = false;
18551
+ const run = (evt) => {
18552
+ if (running) return;
18553
+ running = true;
18554
+ try {
18555
+ if (this.evaluateClauseGroups(clauseGroups)) for (const thenCondition of thenConditions) this.execResult(thenCondition, evt);
18556
+ } finally {
18557
+ running = false;
18558
+ }
18559
+ };
18560
+ const watchedAddresses = new Set(ifConditions.flatMap(({ source, target }) => [source, target]).filter(isAddress));
18561
+ for (const address of watchedAddresses) {
18452
18562
  const { component, options } = this.getComponent(address);
18453
- this.handleComponentCondition(options || component, ifCondition, thenConditions);
18563
+ this.listenForChanges(options || component, run);
18564
+ }
18565
+ run({ target: null });
18566
+ };
18567
+ /**
18568
+ * @param {Array<Array<Object>>} clauseGroups output of groupIfConditions
18569
+ * @return {Boolean} true when every clause of at least one group matches
18570
+ */
18571
+ evaluateClauseGroups = (clauseGroups) => clauseGroups.some((group) => group.length && group.every((clause) => this.evaluateCondition(clause)));
18572
+ listenForChanges = (component, handler) => {
18573
+ if (!component) return;
18574
+ if (isNodeCollection(component)) {
18575
+ for (const elem of component) this.listenForChanges(elem, handler);
18576
+ return;
18454
18577
  }
18578
+ const listenerEvent = LISTEN_TYPE_MAP(component);
18579
+ if (listenerEvent) component.addEventListener(listenerEvent, handler, false);
18455
18580
  };
18456
18581
  /**
18457
18582
  * Evaulate conditions
18458
18583
  */
18459
18584
  evaluateCondition = ({ source, sourceProperty, targetProperty, comparison, target }) => {
18585
+ if (!isAddress(source) || !this.getComponent(source)?.component) return false;
18460
18586
  const sourceValue = this.getComponentProperty(source, sourceProperty);
18461
18587
  if (typeof sourceValue === "boolean") return sourceValue;
18462
18588
  const targetValue = String(isAddress(target) ? this.getComponentProperty(target, targetProperty) : target);
@@ -18483,7 +18609,10 @@ Author: Draggable https://draggable.io
18483
18609
  const result = { component: null };
18484
18610
  if (!isAddress(address)) return null;
18485
18611
  const [, componentId, optionsKey, optionIndex] = splitAddress(address);
18486
- const component = this.renderedForm.querySelector(`#f-${componentId}`);
18612
+ let component = null;
18613
+ try {
18614
+ component = this.renderedForm.querySelector(`#f-${componentId}`);
18615
+ } catch {}
18487
18616
  if (!component) return result;
18488
18617
  result.component = component;
18489
18618
  if (optionsKey) {
@@ -18502,6 +18631,11 @@ Author: Draggable https://draggable.io
18502
18631
  return components;
18503
18632
  };
18504
18633
  };
18634
+ var isCheckable = (elem) => ["checkbox", "radio"].includes(elem?.type);
18635
+ var checkableInputs = (fields) => {
18636
+ if (isCheckable(fields)) return [fields];
18637
+ return fields?.length && isCheckable(fields[0]) ? Array.from(fields) : null;
18638
+ };
18505
18639
  var isDomNode = (value) => Boolean(value) && typeof value.nodeType === "number";
18506
18640
  var isNodeCollection = (value) => Boolean(value) && !isDomNode(value) && typeof value.length === "number";
18507
18641
  var tagName = (component) => component.tagName?.toLowerCase();
@@ -1,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  formeo - https://formeo.io
4
- Version: 5.1.3
4
+ Version: 5.2.1
5
5
  Author: Draggable https://draggable.io
6
6
  */
7
7
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  formeo - https://formeo.io
4
- Version: 5.1.3
4
+ Version: 5.2.1
5
5
  Author: Draggable https://draggable.io
6
6
  */
7
7
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  formeo - https://formeo.io
4
- Version: 5.1.3
4
+ Version: 5.2.1
5
5
  Author: Draggable https://draggable.io
6
6
  */
7
7
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  /**
3
3
  formeo - https://formeo.io
4
- Version: 5.1.3
4
+ Version: 5.2.1
5
5
  Author: Draggable https://draggable.io
6
6
  */
7
7
 
@@ -1 +1 @@
1
- <!doctype html><html lang="en" xml:lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="chrome=1"><meta name="viewport" content="user-scalable=no,width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1"><meta name="description" content="Vanilla Javascript form building module"><meta name="theme-color" content="#232323"><title>Formeo | Drag &amp; Drop Form Creation</title><script type="module" crossorigin src="/formeo/assets/js/demo.min.js"></script><link rel="stylesheet" crossorigin href="/formeo/assets/css/demo.min.css"></head><body><div class="site-wrap"><header id="demo-header"><div class="header-inner"><img id="formeo-logo" src="/formeo/assets/img/formeo-logo.svg" alt="Formeo Logo" width="100"><nav aria-label="Framework navigation"><ul><li><label for="framework-select">Framework:</label> <select name="framework-select" id="framework-select" class="form-control"><option value="vanilla">Vanilla JavaScript</option><option value="angular">Angular</option><option value="react">React</option></select></li><li><label for="locale">Language</label> <select name="locale" id="locale" class="form-control"><option value="af-ZA" dir="ltr">Afrikaans (Suid-Afrika)</option><option value="ar-TN" dir="rtl">العربية (تونس)</option><option value="cs-CZ" dir="ltr">čeština (Česko)</option><option value="de-DE" dir="ltr">Deutsch (Deutschland)</option><option value="en-US" dir="ltr">English</option><option value="es-ES" dir="ltr">español de España</option><option value="fa-IR" dir="rtl">فارسی (ایران)</option><option value="fi-FI" dir="ltr">suomi (Suomi)</option><option value="fr-FR" dir="ltr">français (France)</option><option value="he-IL" dir="rtl">עברית (ישראל)</option><option value="hi-IN" dir="ltr">हिन्दी (भारत)</option><option value="hu-HU" dir="ltr">magyar (Magyarország)</option><option value="it-IT" dir="ltr">italiano (Italia)</option><option value="ja-JP" dir="ltr">日本語 (日本)</option><option value="nb-NO" dir="ltr">norsk bokmål (Norge)</option><option value="pl-PL" dir="ltr">polski (Polska)</option><option value="pt-BR" dir="ltr">português (Brasil)</option><option value="pt-PT" dir="ltr">português europeu</option><option value="ro-RO" dir="ltr">română (România)</option><option value="ru-RU" dir="ltr">русский (Россия)</option><option value="th-TH" dir="ltr">ไทย (ไทย)</option><option value="tr-TR" dir="ltr">Türkçe (Türkiye)</option><option value="zh-CN" dir="ltr">中文(中国)</option><option value="zh-HK" dir="ltr">中文(中國香港特別行政區)</option></select></li><li><label for="control-filter">Control Filter:</label> <input id="control-filter" placeholder="ex. text" class="form-control"></li><li style="float:right">v5.1.3</li></ul></nav></div></header><section id="main_content" class="inner"><div id="framework-container"></div></section><div class="container render-btn-wrap" id="editor-action-buttons"></div><div id="formData-popover" popover><div class="popover-header"><h3>Test formData</h3><div><button type="button" id="format-json">Format</button> <button type="button" id="collapse-json">Collapse</button> <button type="button" id="copy-json">Copy to Clipboard</button></div></div><pre id="formData-editor"></pre><div class="formData-actions"><button popovertarget="formData-popover" popovertargetaction="hide" type="button">Cancel</button> <button id="submit-formData" type="button">Submit</button></div></div><footer id="demo-footer"><nav aria-label="Footer navigation"><ul><li><a href="https://github.com/Draggable/formeo" target="_blank" title="View project on GitHub"><img src="/formeo/assets/img/github.png" width="77" alt="GitHub"></a></li></ul></nav></footer></div></body></html>
1
+ <!doctype html><html lang="en" xml:lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="chrome=1"><meta name="viewport" content="user-scalable=no,width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1"><meta name="description" content="Vanilla Javascript form building module"><meta name="theme-color" content="#232323"><title>Formeo | Drag &amp; Drop Form Creation</title><script type="module" crossorigin src="/formeo/assets/js/demo.min.js"></script><link rel="stylesheet" crossorigin href="/formeo/assets/css/demo.min.css"></head><body><div class="site-wrap"><header id="demo-header"><div class="header-inner"><img id="formeo-logo" src="/formeo/assets/img/formeo-logo.svg" alt="Formeo Logo" width="100"><nav aria-label="Framework navigation"><ul><li><label for="framework-select">Framework:</label> <select name="framework-select" id="framework-select" class="form-control"><option value="vanilla">Vanilla JavaScript</option><option value="angular">Angular</option><option value="react">React</option></select></li><li><label for="locale">Language</label> <select name="locale" id="locale" class="form-control"><option value="af-ZA" dir="ltr">Afrikaans (Suid-Afrika)</option><option value="ar-TN" dir="rtl">العربية (تونس)</option><option value="cs-CZ" dir="ltr">čeština (Česko)</option><option value="de-DE" dir="ltr">Deutsch (Deutschland)</option><option value="en-US" dir="ltr">English</option><option value="es-ES" dir="ltr">español de España</option><option value="fa-IR" dir="rtl">فارسی (ایران)</option><option value="fi-FI" dir="ltr">suomi (Suomi)</option><option value="fr-FR" dir="ltr">français (France)</option><option value="he-IL" dir="rtl">עברית (ישראל)</option><option value="hi-IN" dir="ltr">हिन्दी (भारत)</option><option value="hu-HU" dir="ltr">magyar (Magyarország)</option><option value="it-IT" dir="ltr">italiano (Italia)</option><option value="ja-JP" dir="ltr">日本語 (日本)</option><option value="nb-NO" dir="ltr">norsk bokmål (Norge)</option><option value="pl-PL" dir="ltr">polski (Polska)</option><option value="pt-BR" dir="ltr">português (Brasil)</option><option value="pt-PT" dir="ltr">português europeu</option><option value="ro-RO" dir="ltr">română (România)</option><option value="ru-RU" dir="ltr">русский (Россия)</option><option value="th-TH" dir="ltr">ไทย (ไทย)</option><option value="tr-TR" dir="ltr">Türkçe (Türkiye)</option><option value="zh-CN" dir="ltr">中文(中国)</option><option value="zh-HK" dir="ltr">中文(中國香港特別行政區)</option></select></li><li><label for="control-filter">Control Filter:</label> <input id="control-filter" placeholder="ex. text" class="form-control"></li><li style="float:right">v5.2.1</li></ul></nav></div></header><section id="main_content" class="inner"><div id="framework-container"></div></section><div class="container render-btn-wrap" id="editor-action-buttons"></div><div id="formData-popover" popover><div class="popover-header"><h3>Test formData</h3><div><button type="button" id="format-json">Format</button> <button type="button" id="collapse-json">Collapse</button> <button type="button" id="copy-json">Copy to Clipboard</button></div></div><pre id="formData-editor"></pre><div class="formData-actions"><button popovertarget="formData-popover" popovertargetaction="hide" type="button">Cancel</button> <button id="submit-formData" type="button">Submit</button></div></div><footer id="demo-footer"><nav aria-label="Footer navigation"><ul><li><a href="https://github.com/Draggable/formeo" target="_blank" title="View project on GitHub"><img src="/formeo/assets/img/github.png" width="77" alt="GitHub"></a></li></ul></nav></footer></div></body></html>