@3t-transform/threeteeui 0.2.107 → 0.2.108

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.
@@ -32,7 +32,7 @@ const TttxCheckbox = class {
32
32
  render() {
33
33
  const checkBoxIcon = this.checked ? 'check_box' : 'check_box_outline_blank';
34
34
  const renderedIcon = this.indeterminate ? 'indeterminate_check_box' : checkBoxIcon;
35
- return (index.h(index.Host, null, index.h("label", { class: `tttx-checkbox ${this.inline ? '--inline' : ''}` }, index.h("input", { class: "tttx-checkbox__input", type: "checkbox", id: this.checkboxId, checked: this.checked, ref: (el) => this.checkbox = el }), index.h("tttx-icon", { color: this.checked ? 'blue' : null, icon: renderedIcon, onClick: this.onClick.bind(this) }), index.h("span", { class: "tttx-checkbox__label" }, this.label))));
35
+ return (index.h(index.Host, null, index.h("label", { class: `tttx-checkbox ${this.inline ? '--inline' : ''}` }, index.h("input", { class: "tttx-checkbox__input", type: "checkbox", id: this.checkboxId, checked: this.checked, ref: (el) => this.checkbox = el }), index.h("tttx-icon", { color: this.checked ? 'blue' : 'grey', icon: renderedIcon, onClick: this.onClick.bind(this) }), index.h("span", { class: "tttx-checkbox__label" }, this.label))));
36
36
  }
37
37
  static get watchers() { return {
38
38
  "indeterminate": ["watchIndeterminateChange"]
@@ -16,7 +16,9 @@ require('./_commonjsHelpers-537d719a.js');
16
16
  */
17
17
  function validityCheck(event) {
18
18
  var _a, _b, _c, _d;
19
- event.preventDefault();
19
+ if (event.preventDefault) {
20
+ event.preventDefault();
21
+ }
20
22
  const target = event.target;
21
23
  let hasError = true;
22
24
  let errorMessage = '';
@@ -129,7 +131,7 @@ function setErrorState(target, hasError, errorMessage, parent = undefined) {
129
131
  }
130
132
  }
131
133
 
132
- const tttxFormCss = ".material-symbols-rounded{font-variation-settings:\"FILL\" 1, \"wght\" 400, \"GRAD\" 0, \"opsz\" 24}.material-symbols-rounded{font-variation-settings:\"FILL\" 1, \"wght\" 400, \"GRAD\" 0, \"opsz\" 24}label{font-weight:500;font-size:16px;line-height:19px;color:#212121}label .optional{color:#757575;font-weight:normal}label .outer-container{position:relative}label .outer-container .left-icons,label .outer-container .right-icons{display:flex;position:absolute;height:24px;gap:8px}label .outer-container .left-icons tttx-icon,label .outer-container .right-icons tttx-icon{height:24px;width:24px}label .outer-container .left-icons{left:8px}label .outer-container .right-icons{right:8px}label .outer-container input{color:#212121;box-sizing:border-box;border:1px solid #d5d5d5;border-radius:4px;padding:0;padding-left:16px;padding-right:16px;margin-top:4px;}label .outer-container input.has-input-icon{padding-left:40px}label .outer-container input.has-input-icon.has-left-icon{padding-left:72px}label .outer-container input.has-left-icon{padding-left:40px}label .outer-container input.has-right-icon{padding-right:40px}label .outer-container input.invalid{border:1px solid #dc0000}label .outer-container input:not([type=submit]){font-family:\"Roboto\", serif;width:100%;height:36px;font-size:16px;line-height:19px}label .outer-container input[type=radio]{width:20px;height:20px}label .outer-container input[type=date]{background:white;display:block;min-width:calc(100% - 18px);line-height:37px}label .outer-container input[readonly]{cursor:default;pointer-events:none;user-select:none;color:gray}label .outer-container input:focus{border-color:#1479c6}label .outer-container input:focus-visible{outline:none}label .outer-container.inputBlock{display:flex;align-items:center;line-height:21px}label .outer-container.inputBlock .left-icons,label .outer-container.inputBlock .right-icons{margin-top:4px}label .outer-container.inputBlock.readonly{pointer-events:none;user-select:none;color:gray}label .outer-container.inputBlock.radioBlock{display:block}label .outer-container.inputInline{display:flex;white-space:nowrap;align-items:center;margin:0}label .outer-container.inputInline input{margin-top:0}label .secondarylabel{color:#757575;font-size:14px;line-height:16px;font-weight:normal;display:flex;margin-top:4px}label .errorBubble{position:relative;font-size:14px;line-height:16px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;display:flex;align-content:center;align-items:center;justify-items:center;margin-top:4px}label .errorBubble:not(.visible){display:none}label .errorBubble span{color:#dc0000;font-size:16px;margin-right:4px}.material-symbols-rounded{font-family:\"Material Symbols Rounded\", sans-serif;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;color:#9e9e9e}button{cursor:pointer}.button{font-family:Roboto, serif;box-sizing:border-box;height:36px;min-width:36px;padding:0;margin:0;background:transparent;color:#212121;border:1px solid #c8c8c8;border-radius:4px;text-transform:uppercase;display:flex;justify-content:left;align-items:center;font-size:14px;font-weight:500}.button-content{display:block;padding:0 16px}.icon-left,.icon-right{margin-top:4px}.iconleft{padding-left:8px}.iconleft .button-content{padding-left:4px}.iconright{padding-right:8px}.iconright .button-content{padding-right:4px}.notext{padding:0 6px}.button:active{background:rgba(17, 17, 17, 0.2);border:1px solid #d5d5d5}.primary{background:#1479c6;border:1px solid #1479c6;color:white}.primary:active{background:#1464a2;border:1px solid #1464a2}.borderless{background:transparent;border:none;color:#212121}.borderless:active{background:rgba(17, 17, 17, 0.2);border:none}.borderless-circle{background:transparent;border:none;color:#212121;border-radius:50%}.borderless-circle:active{border:none}.borderless-circle:focus{border-color:transparent}.danger{background:#dc0000;border:1px solid #dc0000;color:white}.danger:active{background:#b00000;border:1px solid #b00000}.disabled{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}.disabled:active{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}@media (hover: hover){.button:hover{background:rgba(17, 17, 17, 0.1);border:1px solid #d5d5d5}.primary:hover{background:#146eb3;border:1px solid #146eb3}.borderless:hover{background:rgba(17, 17, 17, 0.1);border:none}.borderless-circle:hover{background:rgba(17, 17, 17, 0.1);border:none}.danger:hover{background:#c60000;border:1px solid #c60000}.disabled:hover{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}}:host{display:block}fieldset{margin:0;padding:0;border:none}label{display:block;position:relative;margin-bottom:16px}.inlineLabel{font-weight:400;display:inline-block;vertical-align:top;padding-top:4px}input[type=checkbox]{width:18px;height:18px}input~label{font-weight:400}select{font-family:\"Roboto\", serif;box-sizing:border-box;width:100%;height:36px;padding:0 16px;font-size:16px;border:1px solid #d5d5d5;border-radius:4px;margin-top:4px}.placeholder{color:#9e9e9e}.placeholder option{color:initial}select.invalid:invalid{border:1px solid #dc0000}select~.errorBubble{position:relative;font-size:14px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;display:flex;align-content:center;align-items:center;justify-items:center}select~.errorBubble:not(.visible){visibility:hidden}select~.errorBubble span{color:#dc0000;font-size:16px;margin-right:4px;height:16px}select.invalid:invalid~.errorBubble{position:relative;font-size:14px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;visibility:visible}select:focus{border-color:#1479c6}select:focus-visible{outline:none}.button{padding:0 16px}.footer{display:flex;gap:16px;flex-direction:row-reverse}";
134
+ const tttxFormCss = ".material-symbols-rounded{font-variation-settings:\"FILL\" 1, \"wght\" 400, \"GRAD\" 0, \"opsz\" 24}.material-symbols-rounded{font-variation-settings:\"FILL\" 1, \"wght\" 400, \"GRAD\" 0, \"opsz\" 24}label{font-weight:500;font-size:16px;line-height:19px;color:#212121}label .optional{color:#757575;font-weight:normal}label .outer-container{position:relative}label .outer-container .left-icons,label .outer-container .right-icons{display:flex;position:absolute;height:24px;gap:8px}label .outer-container .left-icons tttx-icon,label .outer-container .right-icons tttx-icon{height:24px;width:24px}label .outer-container .left-icons{left:8px}label .outer-container .right-icons{right:8px}label .outer-container input{color:#212121;box-sizing:border-box;border:1px solid #d5d5d5;border-radius:4px;padding:0;padding-left:16px;padding-right:16px;margin-top:4px;}label .outer-container input.has-input-icon{padding-left:40px}label .outer-container input.has-input-icon.has-left-icon{padding-left:72px}label .outer-container input.has-left-icon{padding-left:40px}label .outer-container input.has-right-icon{padding-right:40px}label .outer-container input.invalid{border:1px solid #dc0000}label .outer-container input:not([type=submit]){font-family:\"Roboto\", serif;width:100%;height:36px;font-size:16px;line-height:19px}label .outer-container input[type=radio]{width:20px;height:20px}label .outer-container input[type=date]{background:white;display:block;min-width:calc(100% - 18px);line-height:37px}label .outer-container input[readonly]{cursor:default;pointer-events:none;user-select:none;color:gray}label .outer-container input:focus{border-color:#1479c6}label .outer-container input:focus-visible{outline:none}label .outer-container.inputBlock{display:flex;align-items:center;line-height:21px}label .outer-container.inputBlock .left-icons,label .outer-container.inputBlock .right-icons{margin-top:4px}label .outer-container.inputBlock.readonly{pointer-events:none;user-select:none;color:gray}label .outer-container.inputBlock.radioBlock{display:block}label .outer-container.inputInline{display:flex;white-space:nowrap;align-items:center;margin:0}label .outer-container.inputInline input{margin-top:0}label .secondarylabel{color:#757575;font-size:14px;line-height:16px;font-weight:normal;display:flex;margin-top:4px}label .errorBubble{position:relative;font-size:14px;line-height:16px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;display:flex;align-content:center;align-items:center;justify-items:center;margin-top:4px}label .errorBubble:not(.visible){display:none}label .errorBubble span{color:#dc0000;font-size:16px;margin-right:4px}.material-symbols-rounded{font-family:\"Material Symbols Rounded\", sans-serif;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;color:#9e9e9e}button{cursor:pointer}.button{font-family:Roboto, serif;box-sizing:border-box;height:36px;min-width:36px;padding:0;margin:0;background:transparent;color:#212121;border:1px solid #c8c8c8;border-radius:4px;text-transform:uppercase;display:flex;justify-content:left;align-items:center;font-size:14px;font-weight:500}.button-content{display:block;padding:0 16px}.icon-left,.icon-right{margin-top:4px}.iconleft{padding-left:8px}.iconleft .button-content{padding-left:4px}.iconright{padding-right:8px}.iconright .button-content{padding-right:4px}.notext{padding:0 6px}.button:active{background:rgba(17, 17, 17, 0.2);border:1px solid #d5d5d5}.primary{background:#1479c6;border:1px solid #1479c6;color:white}.primary:active{background:#1464a2;border:1px solid #1464a2}.borderless{background:transparent;border:none;color:#212121}.borderless:active{background:rgba(17, 17, 17, 0.2);border:none}.borderless-circle{background:transparent;border:none;color:#212121;border-radius:50%}.borderless-circle:active{border:none}.borderless-circle:focus{border-color:transparent}.danger{background:#dc0000;border:1px solid #dc0000;color:white}.danger:active{background:#b00000;border:1px solid #b00000}.disabled{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}.disabled:active{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}@media (hover: hover){.button:hover{background:rgba(17, 17, 17, 0.1);border:1px solid #d5d5d5}.primary:hover{background:#146eb3;border:1px solid #146eb3}.borderless:hover{background:rgba(17, 17, 17, 0.1);border:none}.borderless-circle:hover{background:rgba(17, 17, 17, 0.1);border:none}.danger:hover{background:#c60000;border:1px solid #c60000}.disabled:hover{background:#aeaeae;border:none;color:#4c4c4c;cursor:not-allowed}}:host{display:block}fieldset{margin:0;padding:0;border:none}label{display:block;position:relative;margin-bottom:16px}.inlineLabel{font-weight:400;display:inline-block;vertical-align:top;padding-top:4px}input[type=checkbox]{width:18px;height:18px}input~label{font-weight:400}select{font-family:\"Roboto\", serif;box-sizing:border-box;width:100%;height:36px;padding:0 16px;font-size:16px;border:1px solid #d5d5d5;border-radius:4px;margin-top:4px}.placeholder{color:#9e9e9e}.placeholder option{color:initial}select.invalid:invalid{border:1px solid #dc0000}select~.errorBubble{position:relative;font-size:14px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;display:flex;align-content:center;align-items:center;justify-items:center}select~.errorBubble:not(.visible){visibility:hidden}select~.errorBubble span{color:#dc0000;font-size:16px;margin-right:4px;height:16px}select.invalid:invalid~.errorBubble{position:relative;font-size:14px;font-weight:normal;width:100%;font-family:\"Roboto\", sans-serif;color:#dc0000;visibility:visible}select:focus{border-color:#1479c6}select:focus-visible{outline:none}.button{padding:0 16px}.footer{display:flex;gap:16px;flex-direction:row-reverse}label.flex-label{display:flex;flex-direction:column;width:300px;box-sizing:border-box}label.flex-label input[type=date]{flex-grow:1;min-width:auto}label.flex-label input[type=time]{min-width:auto;text-align:center;width:180px;margin-left:16px}";
133
135
 
134
136
  const TttxForm = class {
135
137
  constructor(hostRef) {
@@ -299,6 +301,59 @@ const TttxForm = class {
299
301
  // Return the input element
300
302
  return input;
301
303
  }
304
+ createStartEndDateComponent(formKey, formProperties) {
305
+ const startDate = document.createElement('input');
306
+ const endDate = document.createElement('input');
307
+ startDate.type = endDate.type = 'date';
308
+ startDate.name = `${formKey}-startdate`;
309
+ endDate.name = `${formKey}-enddate`;
310
+ startDate.setAttribute('data-formkey', formKey);
311
+ endDate.setAttribute('data-formkey', formKey);
312
+ if (formProperties.readonly) {
313
+ startDate.readOnly = true;
314
+ endDate.readOnly = true;
315
+ }
316
+ this.applyValidation(startDate, { required: { message: 'Please enter a start date' }, dateCompare: { to: endDate.name } });
317
+ this.applyValidation(endDate, { required: { message: 'Please enter an end date' }, dateCompare: { with: startDate.name, message: 'End date cannot be before the start date' } });
318
+ let startTime;
319
+ let endTime;
320
+ if (formProperties.includeTime) {
321
+ startTime = document.createElement('input');
322
+ endTime = document.createElement('input');
323
+ startTime.type = endTime.type = 'time';
324
+ startTime.name = `${formKey}-starttime`;
325
+ endTime.name = `${formKey}-endtime`;
326
+ startTime.setAttribute('data-formkey', formKey);
327
+ endTime.setAttribute('data-formkey', formKey);
328
+ if (formProperties.readonly) {
329
+ startTime.readOnly = true;
330
+ endTime.readOnly = true;
331
+ }
332
+ this.applyValidation(startTime, { required: { message: 'Please enter a start time' } });
333
+ this.applyValidation(endTime, { required: { message: 'Please enter an end time' } });
334
+ }
335
+ const startLabel = document.createElement('label');
336
+ startLabel.innerText = 'Start date';
337
+ const endLabel = document.createElement('label');
338
+ endLabel.innerText = 'End date';
339
+ const startContainer = document.createElement('div');
340
+ const endContainer = document.createElement('div');
341
+ startContainer.className = endContainer.className = 'outer-container inputBlock';
342
+ startLabel.className = endLabel.className = 'flex-label';
343
+ startContainer.append(startDate);
344
+ if (startTime) {
345
+ startContainer.append(startTime);
346
+ }
347
+ startLabel.append(startContainer);
348
+ startLabel.append(this.createErrorBubble());
349
+ endContainer.append(endDate);
350
+ if (endTime) {
351
+ endContainer.append(endTime);
352
+ }
353
+ endLabel.append(endContainer);
354
+ endLabel.append(this.createErrorBubble());
355
+ return { start: startLabel, end: endLabel };
356
+ }
302
357
  /**
303
358
  * Applies validation attributes to an input element based on the specified validation object.
304
359
  * If a certain property is present in the object, it will set the corresponding attribute on
@@ -319,6 +374,7 @@ const TttxForm = class {
319
374
  * @param {string} [validation.minmax.max]
320
375
  * @param {string} [validation.minmax.message]
321
376
  * @param {string} [validation.maxlength] - The maximum length of the input field.
377
+ * @param {string} [validation.datecompare] - To compare start and end date fields.
322
378
  * @return {void}
323
379
  */
324
380
  applyValidation(input, validation) {
@@ -351,6 +407,16 @@ const TttxForm = class {
351
407
  if (validation.maxlength) {
352
408
  input.setAttribute('maxlength', validation.maxlength);
353
409
  }
410
+ // Custom validation parameters for start date and end date comparison
411
+ if (validation.dateCompare) {
412
+ if (validation.dateCompare.message && validation.dateCompare.with) {
413
+ input.setAttribute('data-compare', validation.dateCompare.message);
414
+ input.setAttribute('data-compare-with', validation.dateCompare.with);
415
+ }
416
+ if (validation.dateCompare.to) {
417
+ input.setAttribute('data-compare-to', validation.dateCompare.to);
418
+ }
419
+ }
354
420
  }
355
421
  // Create a new error bubble element
356
422
  createErrorBubble() {
@@ -467,6 +533,7 @@ const TttxForm = class {
467
533
  * @return {void}
468
534
  */
469
535
  populateFormFromSchema() {
536
+ var _a;
470
537
  // If there is no form schema, return early
471
538
  if (!this._formSchema)
472
539
  return;
@@ -474,9 +541,17 @@ const TttxForm = class {
474
541
  const properties = this._formSchema.properties;
475
542
  const propertyKeys = Object.keys(properties);
476
543
  // Loop through each property key and create an input, label, and error bubble for it
477
- propertyKeys.forEach(formKey => {
544
+ for (const formKey of propertyKeys) {
478
545
  const formItem = properties[formKey];
479
546
  const formProperties = formItem.form;
547
+ // complex form types which require
548
+ // custom HTML should be done here
549
+ if (formProperties.type === 'startenddate') {
550
+ const { start, end } = this.createStartEndDateComponent(formKey, formProperties);
551
+ this.template.content.append(start);
552
+ this.template.content.append(end);
553
+ continue;
554
+ }
480
555
  let input;
481
556
  switch (formProperties.type) {
482
557
  case 'select':
@@ -489,15 +564,22 @@ const TttxForm = class {
489
564
  input = this.createInput(formKey, formProperties);
490
565
  }
491
566
  // If the form property has validation, apply it to the input
492
- if (formProperties.validation && formProperties.type !== 'radio') {
567
+ if (formProperties.validation &&
568
+ formProperties.type !== 'radio') {
493
569
  this.applyValidation(input, formProperties.validation);
494
570
  }
495
571
  // Create an error bubble and label element for the input
496
572
  const errorBubble = this.createErrorBubble();
497
573
  const label = this.createLabel(formProperties, input, errorBubble);
574
+ // If explicitly setting input as invalid, set invalid state and error message on render
575
+ if ((_a = formProperties.validation) === null || _a === void 0 ? void 0 : _a.invalid) {
576
+ const errorMessage = formProperties.validation.invalid.message;
577
+ input.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
578
+ setErrorState(input, true, errorMessage);
579
+ }
498
580
  // Append the label element to the form template
499
581
  this.template.content.append(label);
500
- });
582
+ }
501
583
  }
502
584
  /**
503
585
  * Clones the form template and binds event listeners to its input elements. First, it checks if
@@ -518,47 +600,121 @@ const TttxForm = class {
518
600
  // Bind event listeners to form elements
519
601
  const properties = this._formSchema.properties;
520
602
  const propertyKeys = Object.keys(properties);
521
- propertyKeys.forEach(formKey => {
522
- var _a, _b;
523
- const formInputs = formItems.querySelectorAll(`[name=${formKey}]`);
524
- for (const formInput of formInputs) {
603
+ for (const formKey of propertyKeys) {
604
+ const formItemsByKey = formItems.querySelectorAll(`[name^=${formKey}]`);
605
+ for (const formInput of formItemsByKey) {
525
606
  // Bind events to form input elements
526
607
  formInput.oninvalid = this.validityCheckWrapper.bind(this);
527
608
  formInput.onblur = this.validityCheckWrapper.bind(this);
528
609
  formInput.onkeyup = this.fieldChanged.bind(this);
529
610
  formInput.onchange = this.fieldChanged.bind(this);
530
- if ((_a = this._data) === null || _a === void 0 ? void 0 : _a[formKey]) {
531
- switch (formInput.type) {
611
+ if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
612
+ formInput.addEventListener('change', () => {
613
+ formInput.classList.remove('placeholder');
614
+ });
615
+ }
616
+ if (properties[formKey].form.type === 'startenddate' && formInput.hasAttribute('data-compare-with')) {
617
+ formInput.oninvalid = this.endDateComparisonValidator.bind(this);
618
+ formInput.onblur = this.endDateComparisonValidator.bind(this);
619
+ }
620
+ if (properties[formKey].form.type === 'startenddate' && formInput.type === 'time') {
621
+ formInput.oninvalid = this.startEndTimeComparitor.bind(this);
622
+ formInput.onblur = this.startEndTimeComparitor.bind(this);
623
+ }
624
+ if (properties[formKey].form.type === 'startenddate' && formInput.hasAttribute('data-compare-to')) {
625
+ formInput.oninvalid = this.startDateComparisonValidator.bind(this);
626
+ formInput.onblur = this.startDateComparisonValidator.bind(this);
627
+ }
628
+ }
629
+ }
630
+ // populate with existing form data if available
631
+ if (this._data && Object.keys(this._data).length > 0) {
632
+ const dataKeys = Object.keys(this._data);
633
+ for (const key of dataKeys) {
634
+ const formItemsByKey = formItems.querySelectorAll(`[name=${key}]`);
635
+ for (const formItem of formItemsByKey) {
636
+ switch (formItem.type) {
532
637
  case 'checkbox':
533
- if (this._data[formKey] === 'on') {
534
- formInput.checked = true;
638
+ if (this._data[key] === 'on') {
639
+ formItem.checked = true;
535
640
  }
536
641
  break;
537
642
  case 'radio':
538
- if (formInput.value === this._data[formKey]) {
539
- formInput.checked = true;
643
+ if (formItem.value === this._data[key]) {
644
+ formItem.checked = true;
540
645
  }
541
646
  break;
542
647
  default:
543
- formInput.value = this._data[formKey];
648
+ formItem.value = this._data[key];
544
649
  }
545
650
  }
546
- // If explicitly setting input as invalid, set invalid state and error message on render
547
- if ((_b = properties[formKey].form.validation) === null || _b === void 0 ? void 0 : _b.invalid) {
548
- const errorMessage = properties[formKey].form.validation.invalid.message;
549
- formInput.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
550
- setErrorState(formInput, true, errorMessage);
551
- }
552
- if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
553
- formInput.addEventListener('change', () => {
554
- formInput.classList.remove('placeholder');
555
- });
556
- }
557
651
  }
558
- });
652
+ }
559
653
  // Append the cloned form elements to the fieldset
560
654
  this.fieldset.replaceChildren(formItems);
561
655
  }
656
+ startEndTimeComparitor(event) {
657
+ var _a, _b;
658
+ const target = event.target;
659
+ const formKey = target.getAttribute('data-formkey');
660
+ const timeFields = Array.from(target.parentElement.parentElement.parentElement.querySelectorAll(`[data-formkey=${formKey}]`));
661
+ if (timeFields.length === 4) {
662
+ const startTime = timeFields.find(t => t.name.endsWith('starttime'));
663
+ const endTime = timeFields.find(t => t.name.endsWith('endtime'));
664
+ const startDate = timeFields.find(t => t.name.endsWith('startdate'));
665
+ const endDate = timeFields.find(t => t.name.endsWith('enddate'));
666
+ if (startTime.valueAsNumber >= endTime.valueAsNumber && ((_a = startDate.valueAsDate) === null || _a === void 0 ? void 0 : _a.valueOf()) === ((_b = endDate.valueAsDate) === null || _b === void 0 ? void 0 : _b.valueOf())) {
667
+ setErrorState(endTime, true, 'End time cannot be the same or before the start time');
668
+ if (target.name.endsWith('starttime')) {
669
+ this.validityCheckWrapper(event);
670
+ }
671
+ }
672
+ else {
673
+ // clear any end time comparitor errors and perform the standard validity check on the event
674
+ endTime.setCustomValidity('');
675
+ setErrorState(endTime, false, null);
676
+ this.validityCheckWrapper(event);
677
+ }
678
+ }
679
+ }
680
+ clearTimeComparitor(event) {
681
+ const target = event.target;
682
+ const formKey = target.getAttribute('data-formkey');
683
+ const timeFields = Array.from(target.parentElement.parentElement.parentElement.querySelectorAll(`[type=time][data-formkey=${formKey}]`));
684
+ if (timeFields.length) {
685
+ const endTime = timeFields.find(t => t.name.endsWith('endtime'));
686
+ endTime.setCustomValidity('');
687
+ setErrorState(endTime, false, null);
688
+ }
689
+ }
690
+ startDateComparisonValidator(event) {
691
+ const startDate = event.target;
692
+ const compareToName = startDate.getAttribute('data-compare-to');
693
+ const endDate = startDate.parentElement.parentElement.parentElement.querySelector(`[name=${compareToName}]`);
694
+ this.endDateComparisonValidator({ target: endDate }, true);
695
+ this.validityCheckWrapper(event);
696
+ }
697
+ endDateComparisonValidator(event, triggeredByStartDate = false) {
698
+ var _a, _b, _c, _d;
699
+ const endDate = event.target;
700
+ if (!endDate.value && triggeredByStartDate)
701
+ return;
702
+ const compareWithName = endDate.getAttribute('data-compare-with');
703
+ const startDate = endDate.parentElement.parentElement.parentElement.querySelector(`[name=${compareWithName}]`);
704
+ if (((_a = endDate.valueAsDate) === null || _a === void 0 ? void 0 : _a.valueOf()) < ((_b = startDate.valueAsDate) === null || _b === void 0 ? void 0 : _b.valueOf())) {
705
+ endDate.setCustomValidity(endDate.getAttribute('data-compare'));
706
+ setErrorState(endDate, true, endDate.getAttribute('data-compare'));
707
+ }
708
+ else if (((_c = endDate.valueAsDate) === null || _c === void 0 ? void 0 : _c.valueOf()) === ((_d = startDate.valueAsDate) === null || _d === void 0 ? void 0 : _d.valueOf())) {
709
+ endDate.setCustomValidity('');
710
+ this.validityCheckWrapper(event);
711
+ this.startEndTimeComparitor(event);
712
+ }
713
+ else {
714
+ this.clearTimeComparitor(event);
715
+ this.validityCheckWrapper(event);
716
+ }
717
+ }
562
718
  validityCheckWrapper(event) {
563
719
  const { target, hasError, errorMessage } = validityCheck(event);
564
720
  setErrorState(target, hasError, errorMessage);
@@ -23,7 +23,7 @@ export class TttxCheckbox {
23
23
  render() {
24
24
  const checkBoxIcon = this.checked ? 'check_box' : 'check_box_outline_blank';
25
25
  const renderedIcon = this.indeterminate ? 'indeterminate_check_box' : checkBoxIcon;
26
- return (h(Host, null, h("label", { class: `tttx-checkbox ${this.inline ? '--inline' : ''}` }, h("input", { class: "tttx-checkbox__input", type: "checkbox", id: this.checkboxId, checked: this.checked, ref: (el) => this.checkbox = el }), h("tttx-icon", { color: this.checked ? 'blue' : null, icon: renderedIcon, onClick: this.onClick.bind(this) }), h("span", { class: "tttx-checkbox__label" }, this.label))));
26
+ return (h(Host, null, h("label", { class: `tttx-checkbox ${this.inline ? '--inline' : ''}` }, h("input", { class: "tttx-checkbox__input", type: "checkbox", id: this.checkboxId, checked: this.checked, ref: (el) => this.checkbox = el }), h("tttx-icon", { color: this.checked ? 'blue' : 'grey', icon: renderedIcon, onClick: this.onClick.bind(this) }), h("span", { class: "tttx-checkbox__label" }, this.label))));
27
27
  }
28
28
  static get is() { return "tttx-checkbox"; }
29
29
  static get originalStyleUrls() {
@@ -8,7 +8,9 @@
8
8
  */
9
9
  function validityCheck(event) {
10
10
  var _a, _b, _c, _d;
11
- event.preventDefault();
11
+ if (event.preventDefault) {
12
+ event.preventDefault();
13
+ }
12
14
  const target = event.target;
13
15
  let hasError = true;
14
16
  let errorMessage = '';
@@ -425,4 +425,23 @@ select:focus-visible {
425
425
  display: flex;
426
426
  gap: 16px;
427
427
  flex-direction: row-reverse;
428
+ }
429
+
430
+ label.flex-label {
431
+ display: flex;
432
+ flex-direction: column;
433
+ width: 300px;
434
+ box-sizing: border-box;
435
+ }
436
+
437
+ label.flex-label input[type=date] {
438
+ flex-grow: 1;
439
+ min-width: auto;
440
+ }
441
+
442
+ label.flex-label input[type=time] {
443
+ min-width: auto;
444
+ text-align: center;
445
+ width: 180px;
446
+ margin-left: 16px;
428
447
  }
@@ -171,6 +171,59 @@ export class TttxForm {
171
171
  // Return the input element
172
172
  return input;
173
173
  }
174
+ createStartEndDateComponent(formKey, formProperties) {
175
+ const startDate = document.createElement('input');
176
+ const endDate = document.createElement('input');
177
+ startDate.type = endDate.type = 'date';
178
+ startDate.name = `${formKey}-startdate`;
179
+ endDate.name = `${formKey}-enddate`;
180
+ startDate.setAttribute('data-formkey', formKey);
181
+ endDate.setAttribute('data-formkey', formKey);
182
+ if (formProperties.readonly) {
183
+ startDate.readOnly = true;
184
+ endDate.readOnly = true;
185
+ }
186
+ this.applyValidation(startDate, { required: { message: 'Please enter a start date' }, dateCompare: { to: endDate.name } });
187
+ this.applyValidation(endDate, { required: { message: 'Please enter an end date' }, dateCompare: { with: startDate.name, message: 'End date cannot be before the start date' } });
188
+ let startTime;
189
+ let endTime;
190
+ if (formProperties.includeTime) {
191
+ startTime = document.createElement('input');
192
+ endTime = document.createElement('input');
193
+ startTime.type = endTime.type = 'time';
194
+ startTime.name = `${formKey}-starttime`;
195
+ endTime.name = `${formKey}-endtime`;
196
+ startTime.setAttribute('data-formkey', formKey);
197
+ endTime.setAttribute('data-formkey', formKey);
198
+ if (formProperties.readonly) {
199
+ startTime.readOnly = true;
200
+ endTime.readOnly = true;
201
+ }
202
+ this.applyValidation(startTime, { required: { message: 'Please enter a start time' } });
203
+ this.applyValidation(endTime, { required: { message: 'Please enter an end time' } });
204
+ }
205
+ const startLabel = document.createElement('label');
206
+ startLabel.innerText = 'Start date';
207
+ const endLabel = document.createElement('label');
208
+ endLabel.innerText = 'End date';
209
+ const startContainer = document.createElement('div');
210
+ const endContainer = document.createElement('div');
211
+ startContainer.className = endContainer.className = 'outer-container inputBlock';
212
+ startLabel.className = endLabel.className = 'flex-label';
213
+ startContainer.append(startDate);
214
+ if (startTime) {
215
+ startContainer.append(startTime);
216
+ }
217
+ startLabel.append(startContainer);
218
+ startLabel.append(this.createErrorBubble());
219
+ endContainer.append(endDate);
220
+ if (endTime) {
221
+ endContainer.append(endTime);
222
+ }
223
+ endLabel.append(endContainer);
224
+ endLabel.append(this.createErrorBubble());
225
+ return { start: startLabel, end: endLabel };
226
+ }
174
227
  /**
175
228
  * Applies validation attributes to an input element based on the specified validation object.
176
229
  * If a certain property is present in the object, it will set the corresponding attribute on
@@ -191,6 +244,7 @@ export class TttxForm {
191
244
  * @param {string} [validation.minmax.max]
192
245
  * @param {string} [validation.minmax.message]
193
246
  * @param {string} [validation.maxlength] - The maximum length of the input field.
247
+ * @param {string} [validation.datecompare] - To compare start and end date fields.
194
248
  * @return {void}
195
249
  */
196
250
  applyValidation(input, validation) {
@@ -223,6 +277,16 @@ export class TttxForm {
223
277
  if (validation.maxlength) {
224
278
  input.setAttribute('maxlength', validation.maxlength);
225
279
  }
280
+ // Custom validation parameters for start date and end date comparison
281
+ if (validation.dateCompare) {
282
+ if (validation.dateCompare.message && validation.dateCompare.with) {
283
+ input.setAttribute('data-compare', validation.dateCompare.message);
284
+ input.setAttribute('data-compare-with', validation.dateCompare.with);
285
+ }
286
+ if (validation.dateCompare.to) {
287
+ input.setAttribute('data-compare-to', validation.dateCompare.to);
288
+ }
289
+ }
226
290
  }
227
291
  // Create a new error bubble element
228
292
  createErrorBubble() {
@@ -339,6 +403,7 @@ export class TttxForm {
339
403
  * @return {void}
340
404
  */
341
405
  populateFormFromSchema() {
406
+ var _a;
342
407
  // If there is no form schema, return early
343
408
  if (!this._formSchema)
344
409
  return;
@@ -346,9 +411,17 @@ export class TttxForm {
346
411
  const properties = this._formSchema.properties;
347
412
  const propertyKeys = Object.keys(properties);
348
413
  // Loop through each property key and create an input, label, and error bubble for it
349
- propertyKeys.forEach(formKey => {
414
+ for (const formKey of propertyKeys) {
350
415
  const formItem = properties[formKey];
351
416
  const formProperties = formItem.form;
417
+ // complex form types which require
418
+ // custom HTML should be done here
419
+ if (formProperties.type === 'startenddate') {
420
+ const { start, end } = this.createStartEndDateComponent(formKey, formProperties);
421
+ this.template.content.append(start);
422
+ this.template.content.append(end);
423
+ continue;
424
+ }
352
425
  let input;
353
426
  switch (formProperties.type) {
354
427
  case 'select':
@@ -361,15 +434,22 @@ export class TttxForm {
361
434
  input = this.createInput(formKey, formProperties);
362
435
  }
363
436
  // If the form property has validation, apply it to the input
364
- if (formProperties.validation && formProperties.type !== 'radio') {
437
+ if (formProperties.validation &&
438
+ formProperties.type !== 'radio') {
365
439
  this.applyValidation(input, formProperties.validation);
366
440
  }
367
441
  // Create an error bubble and label element for the input
368
442
  const errorBubble = this.createErrorBubble();
369
443
  const label = this.createLabel(formProperties, input, errorBubble);
444
+ // If explicitly setting input as invalid, set invalid state and error message on render
445
+ if ((_a = formProperties.validation) === null || _a === void 0 ? void 0 : _a.invalid) {
446
+ const errorMessage = formProperties.validation.invalid.message;
447
+ input.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
448
+ setErrorState(input, true, errorMessage);
449
+ }
370
450
  // Append the label element to the form template
371
451
  this.template.content.append(label);
372
- });
452
+ }
373
453
  }
374
454
  /**
375
455
  * Clones the form template and binds event listeners to its input elements. First, it checks if
@@ -390,47 +470,121 @@ export class TttxForm {
390
470
  // Bind event listeners to form elements
391
471
  const properties = this._formSchema.properties;
392
472
  const propertyKeys = Object.keys(properties);
393
- propertyKeys.forEach(formKey => {
394
- var _a, _b;
395
- const formInputs = formItems.querySelectorAll(`[name=${formKey}]`);
396
- for (const formInput of formInputs) {
473
+ for (const formKey of propertyKeys) {
474
+ const formItemsByKey = formItems.querySelectorAll(`[name^=${formKey}]`);
475
+ for (const formInput of formItemsByKey) {
397
476
  // Bind events to form input elements
398
477
  formInput.oninvalid = this.validityCheckWrapper.bind(this);
399
478
  formInput.onblur = this.validityCheckWrapper.bind(this);
400
479
  formInput.onkeyup = this.fieldChanged.bind(this);
401
480
  formInput.onchange = this.fieldChanged.bind(this);
402
- if ((_a = this._data) === null || _a === void 0 ? void 0 : _a[formKey]) {
403
- switch (formInput.type) {
481
+ if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
482
+ formInput.addEventListener('change', () => {
483
+ formInput.classList.remove('placeholder');
484
+ });
485
+ }
486
+ if (properties[formKey].form.type === 'startenddate' && formInput.hasAttribute('data-compare-with')) {
487
+ formInput.oninvalid = this.endDateComparisonValidator.bind(this);
488
+ formInput.onblur = this.endDateComparisonValidator.bind(this);
489
+ }
490
+ if (properties[formKey].form.type === 'startenddate' && formInput.type === 'time') {
491
+ formInput.oninvalid = this.startEndTimeComparitor.bind(this);
492
+ formInput.onblur = this.startEndTimeComparitor.bind(this);
493
+ }
494
+ if (properties[formKey].form.type === 'startenddate' && formInput.hasAttribute('data-compare-to')) {
495
+ formInput.oninvalid = this.startDateComparisonValidator.bind(this);
496
+ formInput.onblur = this.startDateComparisonValidator.bind(this);
497
+ }
498
+ }
499
+ }
500
+ // populate with existing form data if available
501
+ if (this._data && Object.keys(this._data).length > 0) {
502
+ const dataKeys = Object.keys(this._data);
503
+ for (const key of dataKeys) {
504
+ const formItemsByKey = formItems.querySelectorAll(`[name=${key}]`);
505
+ for (const formItem of formItemsByKey) {
506
+ switch (formItem.type) {
404
507
  case 'checkbox':
405
- if (this._data[formKey] === 'on') {
406
- formInput.checked = true;
508
+ if (this._data[key] === 'on') {
509
+ formItem.checked = true;
407
510
  }
408
511
  break;
409
512
  case 'radio':
410
- if (formInput.value === this._data[formKey]) {
411
- formInput.checked = true;
513
+ if (formItem.value === this._data[key]) {
514
+ formItem.checked = true;
412
515
  }
413
516
  break;
414
517
  default:
415
- formInput.value = this._data[formKey];
518
+ formItem.value = this._data[key];
416
519
  }
417
520
  }
418
- // If explicitly setting input as invalid, set invalid state and error message on render
419
- if ((_b = properties[formKey].form.validation) === null || _b === void 0 ? void 0 : _b.invalid) {
420
- const errorMessage = properties[formKey].form.validation.invalid.message;
421
- formInput.setCustomValidity(errorMessage); // Prevents the invalid styling from resetting on blur
422
- setErrorState(formInput, true, errorMessage);
423
- }
424
- if (properties[formKey].form.type === 'select' && formInput.classList.contains('placeholder')) {
425
- formInput.addEventListener('change', () => {
426
- formInput.classList.remove('placeholder');
427
- });
428
- }
429
521
  }
430
- });
522
+ }
431
523
  // Append the cloned form elements to the fieldset
432
524
  this.fieldset.replaceChildren(formItems);
433
525
  }
526
+ startEndTimeComparitor(event) {
527
+ var _a, _b;
528
+ const target = event.target;
529
+ const formKey = target.getAttribute('data-formkey');
530
+ const timeFields = Array.from(target.parentElement.parentElement.parentElement.querySelectorAll(`[data-formkey=${formKey}]`));
531
+ if (timeFields.length === 4) {
532
+ const startTime = timeFields.find(t => t.name.endsWith('starttime'));
533
+ const endTime = timeFields.find(t => t.name.endsWith('endtime'));
534
+ const startDate = timeFields.find(t => t.name.endsWith('startdate'));
535
+ const endDate = timeFields.find(t => t.name.endsWith('enddate'));
536
+ if (startTime.valueAsNumber >= endTime.valueAsNumber && ((_a = startDate.valueAsDate) === null || _a === void 0 ? void 0 : _a.valueOf()) === ((_b = endDate.valueAsDate) === null || _b === void 0 ? void 0 : _b.valueOf())) {
537
+ setErrorState(endTime, true, 'End time cannot be the same or before the start time');
538
+ if (target.name.endsWith('starttime')) {
539
+ this.validityCheckWrapper(event);
540
+ }
541
+ }
542
+ else {
543
+ // clear any end time comparitor errors and perform the standard validity check on the event
544
+ endTime.setCustomValidity('');
545
+ setErrorState(endTime, false, null);
546
+ this.validityCheckWrapper(event);
547
+ }
548
+ }
549
+ }
550
+ clearTimeComparitor(event) {
551
+ const target = event.target;
552
+ const formKey = target.getAttribute('data-formkey');
553
+ const timeFields = Array.from(target.parentElement.parentElement.parentElement.querySelectorAll(`[type=time][data-formkey=${formKey}]`));
554
+ if (timeFields.length) {
555
+ const endTime = timeFields.find(t => t.name.endsWith('endtime'));
556
+ endTime.setCustomValidity('');
557
+ setErrorState(endTime, false, null);
558
+ }
559
+ }
560
+ startDateComparisonValidator(event) {
561
+ const startDate = event.target;
562
+ const compareToName = startDate.getAttribute('data-compare-to');
563
+ const endDate = startDate.parentElement.parentElement.parentElement.querySelector(`[name=${compareToName}]`);
564
+ this.endDateComparisonValidator({ target: endDate }, true);
565
+ this.validityCheckWrapper(event);
566
+ }
567
+ endDateComparisonValidator(event, triggeredByStartDate = false) {
568
+ var _a, _b, _c, _d;
569
+ const endDate = event.target;
570
+ if (!endDate.value && triggeredByStartDate)
571
+ return;
572
+ const compareWithName = endDate.getAttribute('data-compare-with');
573
+ const startDate = endDate.parentElement.parentElement.parentElement.querySelector(`[name=${compareWithName}]`);
574
+ if (((_a = endDate.valueAsDate) === null || _a === void 0 ? void 0 : _a.valueOf()) < ((_b = startDate.valueAsDate) === null || _b === void 0 ? void 0 : _b.valueOf())) {
575
+ endDate.setCustomValidity(endDate.getAttribute('data-compare'));
576
+ setErrorState(endDate, true, endDate.getAttribute('data-compare'));
577
+ }
578
+ else if (((_c = endDate.valueAsDate) === null || _c === void 0 ? void 0 : _c.valueOf()) === ((_d = startDate.valueAsDate) === null || _d === void 0 ? void 0 : _d.valueOf())) {
579
+ endDate.setCustomValidity('');
580
+ this.validityCheckWrapper(event);
581
+ this.startEndTimeComparitor(event);
582
+ }
583
+ else {
584
+ this.clearTimeComparitor(event);
585
+ this.validityCheckWrapper(event);
586
+ }
587
+ }
434
588
  validityCheckWrapper(event) {
435
589
  const { target, hasError, errorMessage } = validityCheck(event);
436
590
  setErrorState(target, hasError, errorMessage);