@3t-transform/threeteeui 0.1.41 → 0.1.43

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.
Files changed (123) hide show
  1. package/dist/cjs/{domsanitiser.options-277161b9.js → domsanitiser.options-975e3317.js} +12 -12
  2. package/dist/cjs/{index-76f14107.js → index-457ca775.js} +88 -2
  3. package/dist/cjs/loader.cjs.js +4 -3
  4. package/dist/cjs/tttx-button.cjs.entry.js +24 -24
  5. package/dist/cjs/tttx-filter.cjs.entry.js +177 -159
  6. package/dist/cjs/tttx-form.cjs.entry.js +458 -456
  7. package/dist/cjs/tttx-icon.cjs.entry.js +11 -11
  8. package/dist/cjs/tttx-keyvalue-block.cjs.entry.js +59 -59
  9. package/dist/cjs/tttx-list.cjs.entry.js +32 -32
  10. package/dist/cjs/tttx-loading-spinner.cjs.entry.js +16 -16
  11. package/dist/cjs/tttx-sorter.cjs.entry.js +102 -102
  12. package/dist/cjs/tttx-standalone-input.cjs.entry.js +79 -79
  13. package/dist/cjs/tttx-toolbar.cjs.entry.js +10 -10
  14. package/dist/cjs/tttx.cjs.js +7 -3
  15. package/dist/collection/collection-manifest.json +2 -2
  16. package/dist/collection/components/atoms/tttx-button/tttx-button.js +110 -110
  17. package/dist/collection/components/atoms/tttx-button/tttx-button.stories.js +14 -14
  18. package/dist/collection/components/atoms/tttx-icon/tttx-icon.js +62 -62
  19. package/dist/collection/components/atoms/tttx-icon/tttx-icon.stories.js +22 -22
  20. package/dist/collection/components/atoms/tttx-keyvalue-block/tttx-keyvalue-block.js +109 -109
  21. package/dist/collection/components/atoms/tttx-keyvalue-block/tttx-keyvalue-block.stories.js +38 -38
  22. package/dist/collection/components/atoms/tttx-loading-spinner/tttx-loading-spinner.js +67 -67
  23. package/dist/collection/components/atoms/tttx-loading-spinner/tttx-loading-spinner.stories.js +17 -17
  24. package/dist/collection/components/molecules/tttx-filter/tttx-filter.js +369 -334
  25. package/dist/collection/components/molecules/tttx-filter/tttx-filter.stories.js +73 -62
  26. package/dist/collection/components/molecules/tttx-form/lib/setErrorState.js +37 -37
  27. package/dist/collection/components/molecules/tttx-form/lib/validityCheck.js +61 -61
  28. package/dist/collection/components/molecules/tttx-form/tttx-form.js +479 -477
  29. package/dist/collection/components/molecules/tttx-form/tttx-form.stories.js +272 -272
  30. package/dist/collection/components/molecules/tttx-list/tttx-list.js +105 -105
  31. package/dist/collection/components/molecules/tttx-list/tttx-list.stories.js +43 -43
  32. package/dist/collection/components/molecules/tttx-sorter/tttx-sorter.js +224 -224
  33. package/dist/collection/components/molecules/tttx-sorter/tttx-sorter.stories.js +42 -42
  34. package/dist/collection/components/molecules/tttx-standalone-input/tttx-standalone-input.js +759 -759
  35. package/dist/collection/components/molecules/tttx-standalone-input/tttx-standalone-input.stories.js +172 -172
  36. package/dist/collection/components/molecules/tttx-toolbar/tttx-toolbar.js +44 -44
  37. package/dist/collection/components/molecules/tttx-toolbar/tttx-toolbar.stories.js +14 -14
  38. package/dist/collection/components/palette.stories.js +7 -7
  39. package/dist/collection/docs/gettingstarted-developer.stories.js +5 -5
  40. package/dist/collection/icons.js +2838 -2838
  41. package/dist/collection/index.js +1 -1
  42. package/dist/collection/shared/domsanitiser.options.js +14 -14
  43. package/dist/components/domsanitiser.options.js +12 -12
  44. package/dist/components/index.d.ts +9 -0
  45. package/dist/components/index.js +1 -1
  46. package/dist/components/tttx-button.js +48 -48
  47. package/dist/components/tttx-filter.js +210 -191
  48. package/dist/components/tttx-form.js +475 -473
  49. package/dist/components/tttx-icon2.js +28 -28
  50. package/dist/components/tttx-keyvalue-block.js +76 -76
  51. package/dist/components/tttx-list.js +53 -53
  52. package/dist/components/tttx-loading-spinner.js +33 -33
  53. package/dist/components/tttx-sorter.js +130 -130
  54. package/dist/components/tttx-standalone-input.js +130 -130
  55. package/dist/components/tttx-toolbar.js +26 -26
  56. package/dist/esm/{domsanitiser.options-cc420431.js → domsanitiser.options-3c7ded83.js} +12 -12
  57. package/dist/esm/{index-9cde46a5.js → index-d784fb3e.js} +88 -3
  58. package/dist/esm/loader.js +4 -3
  59. package/dist/esm/polyfills/core-js.js +0 -0
  60. package/dist/esm/polyfills/css-shim.js +1 -1
  61. package/dist/esm/polyfills/dom.js +0 -0
  62. package/dist/esm/polyfills/es5-html-element.js +0 -0
  63. package/dist/esm/polyfills/index.js +0 -0
  64. package/dist/esm/polyfills/system.js +0 -0
  65. package/dist/esm/tttx-button.entry.js +24 -24
  66. package/dist/esm/tttx-filter.entry.js +177 -159
  67. package/dist/esm/tttx-form.entry.js +458 -456
  68. package/dist/esm/tttx-icon.entry.js +11 -11
  69. package/dist/esm/tttx-keyvalue-block.entry.js +59 -59
  70. package/dist/esm/tttx-list.entry.js +32 -32
  71. package/dist/esm/tttx-loading-spinner.entry.js +16 -16
  72. package/dist/esm/tttx-sorter.entry.js +102 -102
  73. package/dist/esm/tttx-standalone-input.entry.js +79 -79
  74. package/dist/esm/tttx-toolbar.entry.js +10 -10
  75. package/dist/esm/tttx.js +4 -3
  76. package/dist/tttx/p-0ebffdfc.js +2 -0
  77. package/dist/tttx/{p-dc2a37b0.entry.js → p-1db3704e.entry.js} +1 -1
  78. package/dist/tttx/p-350ddb03.js +3 -0
  79. package/dist/tttx/{p-561224f5.entry.js → p-563605b2.entry.js} +1 -1
  80. package/dist/tttx/{p-aef96333.entry.js → p-798a098a.entry.js} +1 -1
  81. package/dist/tttx/{p-f885f17a.entry.js → p-92cade7f.entry.js} +1 -1
  82. package/dist/tttx/{p-bd1edaed.entry.js → p-aaf02902.entry.js} +1 -1
  83. package/dist/tttx/{p-e53c7f9d.entry.js → p-ab6ce9f6.entry.js} +1 -1
  84. package/dist/tttx/{p-9f1e9cc1.entry.js → p-b720c4ad.entry.js} +1 -1
  85. package/dist/tttx/{p-d2f1aa8e.entry.js → p-cac26a1b.entry.js} +1 -1
  86. package/dist/tttx/p-ec253eea.entry.js +1 -0
  87. package/dist/tttx/p-f702df4f.entry.js +1 -0
  88. package/dist/tttx/tttx.esm.js +1 -1
  89. package/dist/types/components/atoms/tttx-button/tttx-button.d.ts +10 -10
  90. package/dist/types/components/atoms/tttx-button/tttx-button.stories.d.ts +10 -10
  91. package/dist/types/components/atoms/tttx-icon/tttx-icon.d.ts +5 -5
  92. package/dist/types/components/atoms/tttx-icon/tttx-icon.stories.d.ts +20 -20
  93. package/dist/types/components/atoms/tttx-keyvalue-block/tttx-keyvalue-block.d.ts +7 -7
  94. package/dist/types/components/atoms/tttx-keyvalue-block/tttx-keyvalue-block.stories.d.ts +9 -9
  95. package/dist/types/components/atoms/tttx-loading-spinner/tttx-loading-spinner.d.ts +6 -6
  96. package/dist/types/components/atoms/tttx-loading-spinner/tttx-loading-spinner.stories.d.ts +17 -17
  97. package/dist/types/components/molecules/tttx-filter/tttx-filter.d.ts +41 -39
  98. package/dist/types/components/molecules/tttx-filter/tttx-filter.stories.d.ts +70 -68
  99. package/dist/types/components/molecules/tttx-form/lib/setErrorState.d.ts +13 -13
  100. package/dist/types/components/molecules/tttx-form/lib/validityCheck.d.ts +17 -17
  101. package/dist/types/components/molecules/tttx-form/tttx-form.d.ts +133 -133
  102. package/dist/types/components/molecules/tttx-form/tttx-form.stories.d.ts +278 -278
  103. package/dist/types/components/molecules/tttx-list/tttx-list.d.ts +11 -11
  104. package/dist/types/components/molecules/tttx-list/tttx-list.stories.d.ts +14 -14
  105. package/dist/types/components/molecules/tttx-sorter/tttx-sorter.d.ts +19 -19
  106. package/dist/types/components/molecules/tttx-sorter/tttx-sorter.stories.d.ts +30 -30
  107. package/dist/types/components/molecules/tttx-standalone-input/tttx-standalone-input.d.ts +69 -69
  108. package/dist/types/components/molecules/tttx-standalone-input/tttx-standalone-input.stories.d.ts +143 -143
  109. package/dist/types/components/molecules/tttx-toolbar/tttx-toolbar.d.ts +4 -4
  110. package/dist/types/components/molecules/tttx-toolbar/tttx-toolbar.stories.d.ts +13 -13
  111. package/dist/types/components/palette.stories.d.ts +6 -6
  112. package/dist/types/components.d.ts +2 -0
  113. package/dist/types/docs/gettingstarted-developer.stories.d.ts +5 -5
  114. package/dist/types/icons.d.ts +2 -2
  115. package/dist/types/index.d.ts +1 -1
  116. package/dist/types/shared/domsanitiser.options.d.ts +10 -10
  117. package/dist/types/stencil-public-runtime.d.ts +59 -3
  118. package/loader/index.d.ts +9 -0
  119. package/package.json +2 -2
  120. package/dist/tttx/p-3b1be372.entry.js +0 -1
  121. package/dist/tttx/p-b4290a5b.js +0 -3
  122. package/dist/tttx/p-d0ff9ad0.entry.js +0 -1
  123. package/dist/tttx/p-db059a69.js +0 -2
@@ -1,477 +1,479 @@
1
- // Ignore in coverage because this is tested through E2E tests
2
- /* istanbul ignore file */
3
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
- import { h, Host } from '@stencil/core';
5
- import * as DOMPurify from 'dompurify';
6
- import domSanitiserOptions from '../../../shared/domsanitiser.options';
7
- import validityCheck from './lib/validityCheck';
8
- import setErrorState from './lib/setErrorState';
9
- export class TttxForm {
10
- constructor() {
11
- // Create a new template element using the HTMLTemplateElement interface.
12
- this.template = document.createElement('template');
13
- this.formschema = undefined;
14
- this.data = undefined;
15
- }
16
- // This method is called whenever the "formschema" property changes
17
- onFormSchemaChange(newValue) {
18
- // If the formSchema changes and the form data is uncontrolled, store the data since the fields will be removed to avoid form duplication
19
- if (!this.data && this.form && this._formSchema) {
20
- const formData = new FormData(this.form);
21
- this._data = Object.fromEntries(Object.keys(this._formSchema.properties).map((formKey) => {
22
- return [
23
- formKey,
24
- formData.get(formKey)
25
- ];
26
- }));
27
- }
28
- // Check if the new value is a string, indicating that it needs to be parsed
29
- if (typeof newValue === 'string') {
30
- // Parse the string and set the "_formSchema" property
31
- this._formSchema = JSON.parse(newValue);
32
- }
33
- else {
34
- // If the new value is already an object, set the "_formSchema" property directly
35
- this._formSchema = newValue;
36
- }
37
- }
38
- onDataChange(newValue) {
39
- if (typeof newValue === 'string') {
40
- this._data = JSON.parse(newValue);
41
- }
42
- else {
43
- this._data = newValue;
44
- }
45
- }
46
- /**
47
- * Handles the focus event for a form field and emits a "dataChanged" event
48
- * to the parent component with the field name and its new value.
49
- *
50
- * @param {ChangeEvent} event - The focus event triggered by the field.
51
- * @return {void}
52
- */
53
- fieldChanged(event) {
54
- // Extract the name and value of the field from the event
55
- const fieldName = event.target.name;
56
- const fieldValue = event.target.value;
57
- // Emit an event to signal that the field's data has changed
58
- this.dataChanged.emit({ name: fieldName, value: fieldValue });
59
- }
60
- async submit() {
61
- this.submitButton.click();
62
- }
63
- /**
64
- * Submits the form data to the server.
65
- *
66
- * @param {SubmitEvent} event - The event object for the form submission.
67
- * @returns {void}
68
- *
69
- * @example
70
- * const form = document.getElementById('myForm');
71
- * form.addEventListener('submit', (event) => {
72
- * doSubmit(event);
73
- * });
74
- */
75
- doSubmit(event) {
76
- // prevent the form from submitting normally
77
- event.preventDefault();
78
- // create a new FormData object with the form data
79
- const formData = new FormData(event.target);
80
- // emit the form data through the `dataSubmitted` event
81
- this.dataSubmitted.emit(formData);
82
- }
83
- // This method is called before the component is loaded into the DOM
84
- componentWillLoad() {
85
- // Initialize the form schema by calling the "onFormSchemaChange" method with the current "formschema" property
86
- this.onFormSchemaChange(this.formschema);
87
- if (this.data) {
88
- this.onDataChange(this.data);
89
- }
90
- }
91
- // This method is called before the component is rendered
92
- componentWillRender() {
93
- // Clear the template to account for a potential re-render scenario
94
- this.template = document.createElement('template');
95
- // Populate the form from the form schema
96
- this.populateFormFromSchema();
97
- }
98
- /**
99
- * Creates a new HTMLSelectElement with a set of options.
100
- *
101
- * @param {string} formKey - The name of the dropdown field, as specified in the form schema.
102
- * @param {Object} formProperties - An object containing additional properties, such as the field type and options properties.
103
- * @param {'select'} formProperties.type - The type of form field. In this case, it will always be "select".
104
- * @param {Object} formProperties.validation - A set of validation rules for the field.
105
- * @param {Object[]} formProperties.options - A list of properties to pass to the select options.
106
- * @param {string} formProperties.options.label - The visible value of the option.
107
- * @param {string} formProperties.options.value - The actual value of the option.
108
- */
109
- createSelect(formKey, formProperties) {
110
- const select = document.createElement('select');
111
- select.setAttribute('name', formKey);
112
- formProperties.options.forEach(optionProperties => {
113
- this.appendOption(select, optionProperties);
114
- });
115
- return select;
116
- }
117
- /**
118
- * Appends an option to a select element
119
- *
120
- * @param {HTMLSelectElement} select - The select elements to attach the option to.
121
- * @param {value} value - The value of the option.
122
- * @param {label} label - The label which will be displayed on the form for the option.
123
- */
124
- appendOption(select, optionProperties) {
125
- const option = document.createElement('option');
126
- option.setAttribute('value', optionProperties.value);
127
- if (optionProperties.placeholder) {
128
- option.setAttribute('disabled', '');
129
- option.setAttribute('selected', '');
130
- option.setAttribute('hidden', '');
131
- select.classList.add('placeholder');
132
- }
133
- if (optionProperties.label)
134
- option.innerHTML = DOMPurify.sanitize(optionProperties.label, domSanitiserOptions);
135
- select.appendChild(option);
136
- }
137
- /**
138
- * Creates a new HTMLInputElement with the specified name, type, and placeholder (if any),
139
- * and sets its autocomplete and autocapitalization properties to off.
140
- *
141
- * @param {string} formKey - The name of the input field, as specified in the form schema.
142
- * @param {Object} formProperties - An object containing additional properties for the input field, such as its type and placeholder value.
143
- * @param {string} formProperties.type - The type of the input field (e.g., "text", "email", "number", etc.).
144
- * @param {string} [formProperties.placeholder] - An optional placeholder value to display in the input field.
145
- * @return {HTMLInputElement} - The new input element.
146
- */
147
- createInput(formKey, formProperties) {
148
- var _a;
149
- // Create a new <input> element with the specified name and type
150
- const input = document.createElement('input');
151
- input.name = formKey;
152
- input.type = formProperties.type;
153
- // Set the placeholder attribute to the specified value (if any)
154
- input.placeholder = (_a = formProperties.placeholder) !== null && _a !== void 0 ? _a : '';
155
- // Disable autocomplete and autocapitalization
156
- input.autocomplete = 'off';
157
- input.autocapitalize = 'off';
158
- if (formProperties.readonly) {
159
- input.readOnly = true;
160
- }
161
- // Return the input element
162
- return input;
163
- }
164
- /**
165
- * Applies validation attributes to an input element based on the specified validation object.
166
- * If a certain property is present in the object, it will set the corresponding attribute on
167
- * the input element (e.g., "required" will set the "required" and "data-required" attributes,
168
- * "pattern" will set the "pattern" and "data-pattern" attributes, etc.).
169
- *
170
- * @param {HTMLInputElement} input - The input element to apply validation attributes to.
171
- * @param {Object} validation - An object containing the validation rules for the input field.
172
- * @param {Object} [validation.required] - An object containing a "message" property to display if the field is required.
173
- * @param {Object} [validation.pattern] - An object containing a "pattern" property to match against the field value, and a "message" property to display if the pattern doesn't match.
174
- * @param {Object} [validation.badInput] - An object containing a "message" property to display if the field value is invalid.
175
- * @param {Object} [validation.minmax] - An object containing "min" and "max" properties to validate the field value against, and a "message" property to display if the value is out of range.
176
- * @param {string} [validation.maxlength] - The maximum length of the input field.
177
- * @return {void}
178
- */
179
- applyValidation(input, validation) {
180
- var _a, _b;
181
- // If the "required" property is present, add the "required" attribute to the input element and
182
- // set its "data-required" attribute to the specified message (if any)
183
- if (validation.required) {
184
- input.setAttribute('required', '');
185
- input.setAttribute('data-required', (_a = validation.required.message) !== null && _a !== void 0 ? _a : '');
186
- }
187
- // If the "pattern" property is present, add the "pattern" attribute to the input element and set
188
- // its "data-pattern" attribute to the specified message (if any)
189
- if (validation.pattern) {
190
- input.setAttribute('pattern', validation.pattern.pattern);
191
- input.setAttribute('data-pattern', (_b = validation.pattern.message) !== null && _b !== void 0 ? _b : '');
192
- }
193
- // If the "badInput" property is present, set the input element's "data-badinput" attribute to
194
- // the specified message
195
- if (validation.badInput) {
196
- input.setAttribute('data-badinput', validation.badInput.message);
197
- }
198
- // If the "minmax" property is present, add the "min" and "max" attributes to the input element
199
- // and set its "data-range" attribute to the specified message (if any)
200
- if (validation.minmax) {
201
- input.setAttribute('min', validation.minmax.min);
202
- input.setAttribute('max', validation.minmax.max);
203
- input.setAttribute('data-range', validation.minmax.message);
204
- }
205
- // If the "maxlength" property is present, add the "maxlength" attribute to the input element
206
- if (validation.maxlength) {
207
- input.setAttribute('maxlength', validation.maxlength);
208
- }
209
- }
210
- // Create a new error bubble element
211
- createErrorBubble() {
212
- // Create a new <div> element with the "errorBubble" class
213
- const errorBubble = document.createElement('div');
214
- errorBubble.className = 'errorBubble';
215
- // Return the error bubble element
216
- return errorBubble;
217
- }
218
- appendCheckboxInput(formProperties, input, label) {
219
- if (formProperties.label) {
220
- const lineBreak = document.createElement('br');
221
- label.appendChild(lineBreak);
222
- }
223
- // Append the input element and error bubble element to the label
224
- label.appendChild(input);
225
- if (!formProperties.inlineLabel)
226
- return;
227
- const inlineLabel = document.createElement('span');
228
- inlineLabel.className = 'inlineLabel';
229
- inlineLabel.textContent = formProperties.inlineLabel;
230
- label.appendChild(inlineLabel);
231
- }
232
- /**
233
- * Creates a new <label> element with the "inputBlock" class and the specified label text,
234
- * and appends the input element and error bubble element to it. If the form property has
235
- * no validation object, it adds an "optional" span element to the label.
236
- *
237
- * @param {Object} formProperties - An object containing properties for the form field, including its label text and validation rules.
238
- * @param {HTMLInputElement} input - The input element to associate with the label.
239
- * @param {HTMLDivElement} errorBubble - The error bubble element to display error messages in.
240
- * @return {HTMLLabelElement} - The new label element.
241
- */
242
- createLabel(formProperties, input, errorBubble) {
243
- // Create a new <label> element with the "inputBlock" class and the specified text
244
- const label = document.createElement('label');
245
- label.className = 'inputBlock';
246
- label.innerText = formProperties.label;
247
- // If the form property has no validation object, add an "optional" span element to the label
248
- if (!formProperties.validation) {
249
- const optionalSpan = document.createElement('span');
250
- optionalSpan.className = 'optional';
251
- optionalSpan.innerHTML = '&nbsp;(optional)';
252
- label.appendChild(optionalSpan);
253
- }
254
- if (formProperties.readonly) {
255
- label.classList.add('readonly');
256
- }
257
- if (formProperties.type === 'checkbox') {
258
- label.className += ' inlineBlock';
259
- this.appendCheckboxInput(formProperties, input, label);
260
- }
261
- else {
262
- // Append the input element and error bubble element to the label
263
- label.appendChild(input);
264
- }
265
- label.appendChild(errorBubble);
266
- // Return the label element
267
- return label;
268
- }
269
- /**
270
- * Populates the form template with input fields and labels based on the properties of the
271
- * current form schema. For each property in the schema, it creates an input element, applies
272
- * any validation rules to it, creates an error bubble and label element, and appends them
273
- * to the form template. Finally, it creates and appends a submit button element to the form.
274
- *
275
- * @return {void}
276
- */
277
- populateFormFromSchema() {
278
- // If there is no form schema, return early
279
- if (!this._formSchema)
280
- return;
281
- // Get the properties of the form schema and their keys
282
- const properties = this._formSchema.properties;
283
- const propertyKeys = Object.keys(properties);
284
- // Loop through each property key and create an input, label, and error bubble for it
285
- propertyKeys.forEach(formKey => {
286
- const formItem = properties[formKey];
287
- const formProperties = formItem.form;
288
- const input = formProperties.type === 'select' ? this.createSelect(formKey, formProperties) : this.createInput(formKey, formProperties);
289
- // If the form property has validation, apply it to the input
290
- if (formProperties.validation) {
291
- this.applyValidation(input, formProperties.validation);
292
- }
293
- // Create an error bubble and label element for the input
294
- const errorBubble = this.createErrorBubble();
295
- const label = this.createLabel(formProperties, input, errorBubble);
296
- // Append the label element to the form template
297
- this.template.content.append(label);
298
- });
299
- }
300
- /**
301
- * Clones the form template and binds event listeners to its input elements. First, it checks if
302
- * there is a form schema present. If so, it clones the template's content, binds events to form
303
- * input elements, and appends the cloned form elements to the fieldset. The event listeners include
304
- * "oninvalid" (to check input validity on submit), "onblur" (to check input validity on blur),
305
- * "onkeyup" (to handle changes in input fields), and "onchange" (to handle changes in select fields).
306
- *
307
- * @return {void}
308
- */
309
- componentDidRender() {
310
- // If there's no form schema, return
311
- if (!this._formSchema) {
312
- return;
313
- }
314
- // Clone the template's content and store it in a variable
315
- const formItems = this.template.content.cloneNode(true);
316
- // Bind event listeners to form elements
317
- const properties = this._formSchema.properties;
318
- const propertyKeys = Object.keys(properties);
319
- propertyKeys.forEach(formKey => {
320
- var _a;
321
- const formInput = formItems.querySelector(`[name=${formKey}]`);
322
- // Bind events to form input elements
323
- formInput.oninvalid = this.validityCheckWrapper.bind(this);
324
- formInput.onblur = this.validityCheckWrapper.bind(this);
325
- formInput.onkeyup = this.fieldChanged.bind(this);
326
- formInput.onchange = this.fieldChanged.bind(this);
327
- if (this._data && this._data[formKey] !== undefined && this._data[formKey] !== null) {
328
- formInput.value = this._data[formKey];
329
- }
330
- // If explicitly setting input as invalid, set invalid state and error message on render
331
- if ((_a = properties[formKey].form.validation) === null || _a === void 0 ? void 0 : _a.invalid) {
332
- const errorMessage = properties[formKey].form.validation.invalid.message;
333
- formInput.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
334
- setErrorState(formInput, true, errorMessage);
335
- }
336
- if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
337
- formInput.addEventListener('change', () => {
338
- formInput.classList.remove('placeholder');
339
- });
340
- }
341
- });
342
- // Append the cloned form elements to the fieldset
343
- this.fieldset.replaceChildren(formItems);
344
- }
345
- validityCheckWrapper(event) {
346
- const { target, hasError, errorMessage } = validityCheck(event);
347
- setErrorState(target, hasError, errorMessage);
348
- }
349
- /**
350
- * Renders the component's template as a form element with a fieldset container. The form's
351
- * "onSubmit" event is bound to the "doSubmit" function, which handles the form submission
352
- * and emits a "dataSubmitted" event with the form data. The fieldset element is assigned
353
- * to the "fieldset" instance variable using a ref, so it can be populated with form elements
354
- * later on.
355
- *
356
- * @return {JSX.Element} - The rendered form template as a JSX element.
357
- */
358
- render() {
359
- return (h(Host, null, h("form", { ref: el => (this.form = el), onSubmit: this.doSubmit.bind(this) }, h("fieldset", { ref: el => (this.fieldset = el) }), h("input", { type: "submit", ref: el => (this.submitButton = el), style: { display: 'none' } }))));
360
- }
361
- static get is() { return "tttx-form"; }
362
- static get encapsulation() { return "shadow"; }
363
- static get originalStyleUrls() {
364
- return {
365
- "$": ["tttx-form.scss"]
366
- };
367
- }
368
- static get styleUrls() {
369
- return {
370
- "$": ["tttx-form.css"]
371
- };
372
- }
373
- static get properties() {
374
- return {
375
- "formschema": {
376
- "type": "any",
377
- "mutable": true,
378
- "complexType": {
379
- "original": "any",
380
- "resolved": "any",
381
- "references": {}
382
- },
383
- "required": false,
384
- "optional": false,
385
- "docs": {
386
- "tags": [],
387
- "text": ""
388
- },
389
- "attribute": "formschema",
390
- "reflect": false
391
- },
392
- "data": {
393
- "type": "any",
394
- "mutable": true,
395
- "complexType": {
396
- "original": "any",
397
- "resolved": "any",
398
- "references": {}
399
- },
400
- "required": false,
401
- "optional": false,
402
- "docs": {
403
- "tags": [],
404
- "text": ""
405
- },
406
- "attribute": "data",
407
- "reflect": false
408
- }
409
- };
410
- }
411
- static get events() {
412
- return [{
413
- "method": "dataSubmitted",
414
- "name": "dataSubmitted",
415
- "bubbles": true,
416
- "cancelable": true,
417
- "composed": true,
418
- "docs": {
419
- "tags": [],
420
- "text": ""
421
- },
422
- "complexType": {
423
- "original": "FormData",
424
- "resolved": "FormData",
425
- "references": {
426
- "FormData": {
427
- "location": "global"
428
- }
429
- }
430
- }
431
- }, {
432
- "method": "dataChanged",
433
- "name": "dataChanged",
434
- "bubbles": true,
435
- "cancelable": true,
436
- "composed": true,
437
- "docs": {
438
- "tags": [],
439
- "text": ""
440
- },
441
- "complexType": {
442
- "original": "{ name: string; value: any }",
443
- "resolved": "{ name: string; value: any; }",
444
- "references": {}
445
- }
446
- }];
447
- }
448
- static get methods() {
449
- return {
450
- "submit": {
451
- "complexType": {
452
- "signature": "() => Promise<void>",
453
- "parameters": [],
454
- "references": {
455
- "Promise": {
456
- "location": "global"
457
- }
458
- },
459
- "return": "Promise<void>"
460
- },
461
- "docs": {
462
- "text": "",
463
- "tags": []
464
- }
465
- }
466
- };
467
- }
468
- static get watchers() {
469
- return [{
470
- "propName": "formschema",
471
- "methodName": "onFormSchemaChange"
472
- }, {
473
- "propName": "data",
474
- "methodName": "onDataChange"
475
- }];
476
- }
477
- }
1
+ // Ignore in coverage because this is tested through E2E tests
2
+ /* istanbul ignore file */
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import { h, Host } from '@stencil/core';
5
+ import * as DOMPurify from 'dompurify';
6
+ import domSanitiserOptions from '../../../shared/domsanitiser.options';
7
+ import validityCheck from './lib/validityCheck';
8
+ import setErrorState from './lib/setErrorState';
9
+ export class TttxForm {
10
+ constructor() {
11
+ // Create a new template element using the HTMLTemplateElement interface.
12
+ this.template = document.createElement('template');
13
+ this.formschema = undefined;
14
+ this.data = undefined;
15
+ }
16
+ // This method is called whenever the "formschema" property changes
17
+ onFormSchemaChange(newValue) {
18
+ // If the formSchema changes and the form data is uncontrolled, store the data since the fields will be removed to avoid form duplication
19
+ if (!this.data && this.form && this._formSchema) {
20
+ const formData = new FormData(this.form);
21
+ this._data = Object.fromEntries(Object.keys(this._formSchema.properties).map((formKey) => {
22
+ return [
23
+ formKey,
24
+ formData.get(formKey)
25
+ ];
26
+ }));
27
+ }
28
+ // Check if the new value is a string, indicating that it needs to be parsed
29
+ if (typeof newValue === 'string') {
30
+ // Parse the string and set the "_formSchema" property
31
+ this._formSchema = JSON.parse(newValue);
32
+ }
33
+ else {
34
+ // If the new value is already an object, set the "_formSchema" property directly
35
+ this._formSchema = newValue;
36
+ }
37
+ }
38
+ onDataChange(newValue) {
39
+ if (typeof newValue === 'string') {
40
+ this._data = JSON.parse(newValue);
41
+ }
42
+ else {
43
+ this._data = newValue;
44
+ }
45
+ }
46
+ /**
47
+ * Handles the focus event for a form field and emits a "dataChanged" event
48
+ * to the parent component with the field name and its new value.
49
+ *
50
+ * @param {ChangeEvent} event - The focus event triggered by the field.
51
+ * @return {void}
52
+ */
53
+ fieldChanged(event) {
54
+ // Extract the name and value of the field from the event
55
+ const fieldName = event.target.name;
56
+ const fieldValue = event.target.value;
57
+ // Emit an event to signal that the field's data has changed
58
+ this.dataChanged.emit({ name: fieldName, value: fieldValue });
59
+ }
60
+ async submit() {
61
+ this.submitButton.click();
62
+ }
63
+ /**
64
+ * Submits the form data to the server.
65
+ *
66
+ * @param {SubmitEvent} event - The event object for the form submission.
67
+ * @returns {void}
68
+ *
69
+ * @example
70
+ * const form = document.getElementById('myForm');
71
+ * form.addEventListener('submit', (event) => {
72
+ * doSubmit(event);
73
+ * });
74
+ */
75
+ doSubmit(event) {
76
+ // prevent the form from submitting normally
77
+ event.preventDefault();
78
+ // create a new FormData object with the form data
79
+ const formData = new FormData(event.target);
80
+ // emit the form data through the `dataSubmitted` event
81
+ this.dataSubmitted.emit(formData);
82
+ }
83
+ // This method is called before the component is loaded into the DOM
84
+ componentWillLoad() {
85
+ // Initialize the form schema by calling the "onFormSchemaChange" method with the current "formschema" property
86
+ this.onFormSchemaChange(this.formschema);
87
+ if (this.data) {
88
+ this.onDataChange(this.data);
89
+ }
90
+ }
91
+ // This method is called before the component is rendered
92
+ componentWillRender() {
93
+ // Clear the template to account for a potential re-render scenario
94
+ this.template = document.createElement('template');
95
+ // Populate the form from the form schema
96
+ this.populateFormFromSchema();
97
+ }
98
+ /**
99
+ * Creates a new HTMLSelectElement with a set of options.
100
+ *
101
+ * @param {string} formKey - The name of the dropdown field, as specified in the form schema.
102
+ * @param {Object} formProperties - An object containing additional properties, such as the field type and options properties.
103
+ * @param {'select'} formProperties.type - The type of form field. In this case, it will always be "select".
104
+ * @param {Object} formProperties.validation - A set of validation rules for the field.
105
+ * @param {Object[]} formProperties.options - A list of properties to pass to the select options.
106
+ * @param {string} formProperties.options.label - The visible value of the option.
107
+ * @param {string} formProperties.options.value - The actual value of the option.
108
+ */
109
+ createSelect(formKey, formProperties) {
110
+ const select = document.createElement('select');
111
+ select.setAttribute('name', formKey);
112
+ formProperties.options.forEach(optionProperties => {
113
+ this.appendOption(select, optionProperties);
114
+ });
115
+ if (this._data && this._data[formKey])
116
+ select.classList.remove('placeholder');
117
+ return select;
118
+ }
119
+ /**
120
+ * Appends an option to a select element
121
+ *
122
+ * @param {HTMLSelectElement} select - The select elements to attach the option to.
123
+ * @param {value} value - The value of the option.
124
+ * @param {label} label - The label which will be displayed on the form for the option.
125
+ */
126
+ appendOption(select, optionProperties) {
127
+ const option = document.createElement('option');
128
+ option.setAttribute('value', optionProperties.value);
129
+ if (optionProperties.placeholder) {
130
+ option.setAttribute('disabled', '');
131
+ option.setAttribute('selected', '');
132
+ option.setAttribute('hidden', '');
133
+ select.classList.add('placeholder');
134
+ }
135
+ if (optionProperties.label)
136
+ option.innerHTML = DOMPurify.sanitize(optionProperties.label, domSanitiserOptions);
137
+ select.appendChild(option);
138
+ }
139
+ /**
140
+ * Creates a new HTMLInputElement with the specified name, type, and placeholder (if any),
141
+ * and sets its autocomplete and autocapitalization properties to off.
142
+ *
143
+ * @param {string} formKey - The name of the input field, as specified in the form schema.
144
+ * @param {Object} formProperties - An object containing additional properties for the input field, such as its type and placeholder value.
145
+ * @param {string} formProperties.type - The type of the input field (e.g., "text", "email", "number", etc.).
146
+ * @param {string} [formProperties.placeholder] - An optional placeholder value to display in the input field.
147
+ * @return {HTMLInputElement} - The new input element.
148
+ */
149
+ createInput(formKey, formProperties) {
150
+ var _a;
151
+ // Create a new <input> element with the specified name and type
152
+ const input = document.createElement('input');
153
+ input.name = formKey;
154
+ input.type = formProperties.type;
155
+ // Set the placeholder attribute to the specified value (if any)
156
+ input.placeholder = (_a = formProperties.placeholder) !== null && _a !== void 0 ? _a : '';
157
+ // Disable autocomplete and autocapitalization
158
+ input.autocomplete = 'off';
159
+ input.autocapitalize = 'off';
160
+ if (formProperties.readonly) {
161
+ input.readOnly = true;
162
+ }
163
+ // Return the input element
164
+ return input;
165
+ }
166
+ /**
167
+ * Applies validation attributes to an input element based on the specified validation object.
168
+ * If a certain property is present in the object, it will set the corresponding attribute on
169
+ * the input element (e.g., "required" will set the "required" and "data-required" attributes,
170
+ * "pattern" will set the "pattern" and "data-pattern" attributes, etc.).
171
+ *
172
+ * @param {HTMLInputElement} input - The input element to apply validation attributes to.
173
+ * @param {Object} validation - An object containing the validation rules for the input field.
174
+ * @param {Object} [validation.required] - An object containing a "message" property to display if the field is required.
175
+ * @param {Object} [validation.pattern] - An object containing a "pattern" property to match against the field value, and a "message" property to display if the pattern doesn't match.
176
+ * @param {Object} [validation.badInput] - An object containing a "message" property to display if the field value is invalid.
177
+ * @param {Object} [validation.minmax] - An object containing "min" and "max" properties to validate the field value against, and a "message" property to display if the value is out of range.
178
+ * @param {string} [validation.maxlength] - The maximum length of the input field.
179
+ * @return {void}
180
+ */
181
+ applyValidation(input, validation) {
182
+ var _a, _b;
183
+ // If the "required" property is present, add the "required" attribute to the input element and
184
+ // set its "data-required" attribute to the specified message (if any)
185
+ if (validation.required) {
186
+ input.setAttribute('required', '');
187
+ input.setAttribute('data-required', (_a = validation.required.message) !== null && _a !== void 0 ? _a : '');
188
+ }
189
+ // If the "pattern" property is present, add the "pattern" attribute to the input element and set
190
+ // its "data-pattern" attribute to the specified message (if any)
191
+ if (validation.pattern) {
192
+ input.setAttribute('pattern', validation.pattern.pattern);
193
+ input.setAttribute('data-pattern', (_b = validation.pattern.message) !== null && _b !== void 0 ? _b : '');
194
+ }
195
+ // If the "badInput" property is present, set the input element's "data-badinput" attribute to
196
+ // the specified message
197
+ if (validation.badInput) {
198
+ input.setAttribute('data-badinput', validation.badInput.message);
199
+ }
200
+ // If the "minmax" property is present, add the "min" and "max" attributes to the input element
201
+ // and set its "data-range" attribute to the specified message (if any)
202
+ if (validation.minmax) {
203
+ input.setAttribute('min', validation.minmax.min);
204
+ input.setAttribute('max', validation.minmax.max);
205
+ input.setAttribute('data-range', validation.minmax.message);
206
+ }
207
+ // If the "maxlength" property is present, add the "maxlength" attribute to the input element
208
+ if (validation.maxlength) {
209
+ input.setAttribute('maxlength', validation.maxlength);
210
+ }
211
+ }
212
+ // Create a new error bubble element
213
+ createErrorBubble() {
214
+ // Create a new <div> element with the "errorBubble" class
215
+ const errorBubble = document.createElement('div');
216
+ errorBubble.className = 'errorBubble';
217
+ // Return the error bubble element
218
+ return errorBubble;
219
+ }
220
+ appendCheckboxInput(formProperties, input, label) {
221
+ if (formProperties.label) {
222
+ const lineBreak = document.createElement('br');
223
+ label.appendChild(lineBreak);
224
+ }
225
+ // Append the input element and error bubble element to the label
226
+ label.appendChild(input);
227
+ if (!formProperties.inlineLabel)
228
+ return;
229
+ const inlineLabel = document.createElement('span');
230
+ inlineLabel.className = 'inlineLabel';
231
+ inlineLabel.textContent = formProperties.inlineLabel;
232
+ label.appendChild(inlineLabel);
233
+ }
234
+ /**
235
+ * Creates a new <label> element with the "inputBlock" class and the specified label text,
236
+ * and appends the input element and error bubble element to it. If the form property has
237
+ * no validation object, it adds an "optional" span element to the label.
238
+ *
239
+ * @param {Object} formProperties - An object containing properties for the form field, including its label text and validation rules.
240
+ * @param {HTMLInputElement} input - The input element to associate with the label.
241
+ * @param {HTMLDivElement} errorBubble - The error bubble element to display error messages in.
242
+ * @return {HTMLLabelElement} - The new label element.
243
+ */
244
+ createLabel(formProperties, input, errorBubble) {
245
+ // Create a new <label> element with the "inputBlock" class and the specified text
246
+ const label = document.createElement('label');
247
+ label.className = 'inputBlock';
248
+ label.innerText = formProperties.label;
249
+ // If the form property has no validation object, add an "optional" span element to the label
250
+ if (!formProperties.validation) {
251
+ const optionalSpan = document.createElement('span');
252
+ optionalSpan.className = 'optional';
253
+ optionalSpan.innerHTML = '&nbsp;(optional)';
254
+ label.appendChild(optionalSpan);
255
+ }
256
+ if (formProperties.readonly) {
257
+ label.classList.add('readonly');
258
+ }
259
+ if (formProperties.type === 'checkbox') {
260
+ label.className += ' inlineBlock';
261
+ this.appendCheckboxInput(formProperties, input, label);
262
+ }
263
+ else {
264
+ // Append the input element and error bubble element to the label
265
+ label.appendChild(input);
266
+ }
267
+ label.appendChild(errorBubble);
268
+ // Return the label element
269
+ return label;
270
+ }
271
+ /**
272
+ * Populates the form template with input fields and labels based on the properties of the
273
+ * current form schema. For each property in the schema, it creates an input element, applies
274
+ * any validation rules to it, creates an error bubble and label element, and appends them
275
+ * to the form template. Finally, it creates and appends a submit button element to the form.
276
+ *
277
+ * @return {void}
278
+ */
279
+ populateFormFromSchema() {
280
+ // If there is no form schema, return early
281
+ if (!this._formSchema)
282
+ return;
283
+ // Get the properties of the form schema and their keys
284
+ const properties = this._formSchema.properties;
285
+ const propertyKeys = Object.keys(properties);
286
+ // Loop through each property key and create an input, label, and error bubble for it
287
+ propertyKeys.forEach(formKey => {
288
+ const formItem = properties[formKey];
289
+ const formProperties = formItem.form;
290
+ const input = formProperties.type === 'select' ? this.createSelect(formKey, formProperties) : this.createInput(formKey, formProperties);
291
+ // If the form property has validation, apply it to the input
292
+ if (formProperties.validation) {
293
+ this.applyValidation(input, formProperties.validation);
294
+ }
295
+ // Create an error bubble and label element for the input
296
+ const errorBubble = this.createErrorBubble();
297
+ const label = this.createLabel(formProperties, input, errorBubble);
298
+ // Append the label element to the form template
299
+ this.template.content.append(label);
300
+ });
301
+ }
302
+ /**
303
+ * Clones the form template and binds event listeners to its input elements. First, it checks if
304
+ * there is a form schema present. If so, it clones the template's content, binds events to form
305
+ * input elements, and appends the cloned form elements to the fieldset. The event listeners include
306
+ * "oninvalid" (to check input validity on submit), "onblur" (to check input validity on blur),
307
+ * "onkeyup" (to handle changes in input fields), and "onchange" (to handle changes in select fields).
308
+ *
309
+ * @return {void}
310
+ */
311
+ componentDidRender() {
312
+ // If there's no form schema, return
313
+ if (!this._formSchema) {
314
+ return;
315
+ }
316
+ // Clone the template's content and store it in a variable
317
+ const formItems = this.template.content.cloneNode(true);
318
+ // Bind event listeners to form elements
319
+ const properties = this._formSchema.properties;
320
+ const propertyKeys = Object.keys(properties);
321
+ propertyKeys.forEach(formKey => {
322
+ var _a;
323
+ const formInput = formItems.querySelector(`[name=${formKey}]`);
324
+ // Bind events to form input elements
325
+ formInput.oninvalid = this.validityCheckWrapper.bind(this);
326
+ formInput.onblur = this.validityCheckWrapper.bind(this);
327
+ formInput.onkeyup = this.fieldChanged.bind(this);
328
+ formInput.onchange = this.fieldChanged.bind(this);
329
+ if (this._data && this._data[formKey] !== undefined && this._data[formKey] !== null) {
330
+ formInput.value = this._data[formKey];
331
+ }
332
+ // If explicitly setting input as invalid, set invalid state and error message on render
333
+ if ((_a = properties[formKey].form.validation) === null || _a === void 0 ? void 0 : _a.invalid) {
334
+ const errorMessage = properties[formKey].form.validation.invalid.message;
335
+ formInput.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
336
+ setErrorState(formInput, true, errorMessage);
337
+ }
338
+ if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
339
+ formInput.addEventListener('change', () => {
340
+ formInput.classList.remove('placeholder');
341
+ });
342
+ }
343
+ });
344
+ // Append the cloned form elements to the fieldset
345
+ this.fieldset.replaceChildren(formItems);
346
+ }
347
+ validityCheckWrapper(event) {
348
+ const { target, hasError, errorMessage } = validityCheck(event);
349
+ setErrorState(target, hasError, errorMessage);
350
+ }
351
+ /**
352
+ * Renders the component's template as a form element with a fieldset container. The form's
353
+ * "onSubmit" event is bound to the "doSubmit" function, which handles the form submission
354
+ * and emits a "dataSubmitted" event with the form data. The fieldset element is assigned
355
+ * to the "fieldset" instance variable using a ref, so it can be populated with form elements
356
+ * later on.
357
+ *
358
+ * @return {JSX.Element} - The rendered form template as a JSX element.
359
+ */
360
+ render() {
361
+ return (h(Host, null, h("form", { ref: el => (this.form = el), onSubmit: this.doSubmit.bind(this) }, h("fieldset", { ref: el => (this.fieldset = el) }), h("input", { type: "submit", ref: el => (this.submitButton = el), style: { display: 'none' } }))));
362
+ }
363
+ static get is() { return "tttx-form"; }
364
+ static get encapsulation() { return "shadow"; }
365
+ static get originalStyleUrls() {
366
+ return {
367
+ "$": ["tttx-form.scss"]
368
+ };
369
+ }
370
+ static get styleUrls() {
371
+ return {
372
+ "$": ["tttx-form.css"]
373
+ };
374
+ }
375
+ static get properties() {
376
+ return {
377
+ "formschema": {
378
+ "type": "any",
379
+ "mutable": true,
380
+ "complexType": {
381
+ "original": "any",
382
+ "resolved": "any",
383
+ "references": {}
384
+ },
385
+ "required": false,
386
+ "optional": false,
387
+ "docs": {
388
+ "tags": [],
389
+ "text": ""
390
+ },
391
+ "attribute": "formschema",
392
+ "reflect": false
393
+ },
394
+ "data": {
395
+ "type": "any",
396
+ "mutable": true,
397
+ "complexType": {
398
+ "original": "any",
399
+ "resolved": "any",
400
+ "references": {}
401
+ },
402
+ "required": false,
403
+ "optional": false,
404
+ "docs": {
405
+ "tags": [],
406
+ "text": ""
407
+ },
408
+ "attribute": "data",
409
+ "reflect": false
410
+ }
411
+ };
412
+ }
413
+ static get events() {
414
+ return [{
415
+ "method": "dataSubmitted",
416
+ "name": "dataSubmitted",
417
+ "bubbles": true,
418
+ "cancelable": true,
419
+ "composed": true,
420
+ "docs": {
421
+ "tags": [],
422
+ "text": ""
423
+ },
424
+ "complexType": {
425
+ "original": "FormData",
426
+ "resolved": "FormData",
427
+ "references": {
428
+ "FormData": {
429
+ "location": "global"
430
+ }
431
+ }
432
+ }
433
+ }, {
434
+ "method": "dataChanged",
435
+ "name": "dataChanged",
436
+ "bubbles": true,
437
+ "cancelable": true,
438
+ "composed": true,
439
+ "docs": {
440
+ "tags": [],
441
+ "text": ""
442
+ },
443
+ "complexType": {
444
+ "original": "{ name: string; value: any }",
445
+ "resolved": "{ name: string; value: any; }",
446
+ "references": {}
447
+ }
448
+ }];
449
+ }
450
+ static get methods() {
451
+ return {
452
+ "submit": {
453
+ "complexType": {
454
+ "signature": "() => Promise<void>",
455
+ "parameters": [],
456
+ "references": {
457
+ "Promise": {
458
+ "location": "global"
459
+ }
460
+ },
461
+ "return": "Promise<void>"
462
+ },
463
+ "docs": {
464
+ "text": "",
465
+ "tags": []
466
+ }
467
+ }
468
+ };
469
+ }
470
+ static get watchers() {
471
+ return [{
472
+ "propName": "formschema",
473
+ "methodName": "onFormSchemaChange"
474
+ }, {
475
+ "propName": "data",
476
+ "methodName": "onDataChange"
477
+ }];
478
+ }
479
+ }