@foxy.io/elements 1.14.0-beta.4 → 1.14.0-beta.8

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 (91) hide show
  1. package/README.md +1 -1
  2. package/dist/cdn/foxy-access-recovery-form.js +1 -1
  3. package/dist/cdn/foxy-address-card.js +1 -1
  4. package/dist/cdn/foxy-address-form.js +1 -1
  5. package/dist/cdn/foxy-applied-tax-card.js +1 -1
  6. package/dist/cdn/foxy-attribute-card.js +1 -1
  7. package/dist/cdn/foxy-attribute-form.js +1 -1
  8. package/dist/cdn/foxy-cancellation-form.js +1 -1
  9. package/dist/cdn/foxy-collection-page.js +1 -1
  10. package/dist/cdn/foxy-collection-pages.js +1 -1
  11. package/dist/cdn/foxy-custom-field-card.js +1 -1
  12. package/dist/cdn/foxy-custom-field-form.js +1 -1
  13. package/dist/cdn/foxy-customer-card.js +1 -1
  14. package/dist/cdn/foxy-customer-form.js +1 -1
  15. package/dist/cdn/foxy-customer-portal-settings.js +17 -17
  16. package/dist/cdn/foxy-customer-portal.js +1 -1
  17. package/dist/cdn/foxy-customer.js +4 -4
  18. package/dist/cdn/foxy-customers-table.js +1 -1
  19. package/dist/cdn/foxy-discount-card.js +1 -1
  20. package/dist/cdn/foxy-donation.js +1 -1
  21. package/dist/cdn/foxy-email-template-form.js +1 -1
  22. package/dist/cdn/foxy-error-entry-card.js +1 -1
  23. package/dist/cdn/foxy-form-dialog.js +1 -1
  24. package/dist/cdn/foxy-i18n.js +1 -1
  25. package/dist/cdn/foxy-items-form.js +1 -1
  26. package/dist/cdn/foxy-payment-card.js +1 -1
  27. package/dist/cdn/foxy-payment-method-card.js +1 -1
  28. package/dist/cdn/foxy-query-builder.js +1 -1
  29. package/dist/cdn/foxy-sign-in-form.js +1 -1
  30. package/dist/cdn/foxy-spinner.js +1 -1
  31. package/dist/cdn/foxy-subscription-card.js +1 -1
  32. package/dist/cdn/foxy-subscription-form.js +3 -3
  33. package/dist/cdn/foxy-subscriptions-table.js +1 -1
  34. package/dist/cdn/foxy-table.js +1 -1
  35. package/dist/cdn/foxy-tax-card.js +1 -1
  36. package/dist/cdn/foxy-tax-form.js +1 -1
  37. package/dist/cdn/foxy-template-config-form.js +1 -14
  38. package/dist/cdn/foxy-template-form.js +1 -1
  39. package/dist/cdn/foxy-transaction-card.js +1 -1
  40. package/dist/cdn/foxy-transactions-table.js +1 -1
  41. package/dist/cdn/foxy-user-form.js +1 -1
  42. package/dist/cdn/foxy-users-table.js +1 -1
  43. package/dist/cdn/{shared-10bdb6b9.js → shared-00070cc4.js} +1 -1
  44. package/dist/cdn/shared-007c4e34.js +1 -0
  45. package/dist/cdn/shared-07134f93.js +14 -0
  46. package/dist/cdn/{shared-c4b96261.js → shared-0f9809ab.js} +1 -1
  47. package/dist/cdn/{shared-18459dcd.js → shared-31d03530.js} +7 -7
  48. package/dist/cdn/{shared-4a52d9b5.js → shared-4ba926ca.js} +1 -1
  49. package/dist/cdn/{shared-106daaca.js → shared-5d94bacb.js} +1 -1
  50. package/dist/cdn/{shared-1469c1c4.js → shared-7007dedb.js} +1 -1
  51. package/dist/cdn/{shared-4be4e513.js → shared-a3d2c48e.js} +1 -1
  52. package/dist/cdn/{shared-fb90e05c.js → shared-b24377bf.js} +1 -1
  53. package/dist/cdn/{shared-2174bcd4.js → shared-c5ae5d33.js} +1 -1
  54. package/dist/cdn/{shared-9d779f46.js → shared-ca7c3b9a.js} +1 -1
  55. package/dist/cdn/shared-d01035c5.js +1 -0
  56. package/dist/cdn/{shared-614e1a4e.js → shared-d05c93a5.js} +1 -1
  57. package/dist/cdn/{shared-7a39a41f.js → shared-da787055.js} +1 -1
  58. package/dist/cdn/{shared-3d868b17.js → shared-daf6b763.js} +1 -1
  59. package/dist/cdn/shared-fe8a7aa2.js +1 -0
  60. package/dist/cdn/translations/shared/en.json +43 -1
  61. package/dist/elements/private/Checkbox/Checkbox.d.ts +3 -1
  62. package/dist/elements/private/Checkbox/Checkbox.js +45 -12
  63. package/dist/elements/private/Checkbox/Checkbox.js.map +1 -1
  64. package/dist/elements/private/Choice/Choice.js +9 -7
  65. package/dist/elements/private/Choice/Choice.js.map +1 -1
  66. package/dist/elements/public/EmailTemplateForm/EmailTemplateForm.d.ts +3 -2
  67. package/dist/elements/public/EmailTemplateForm/EmailTemplateForm.js +69 -51
  68. package/dist/elements/public/EmailTemplateForm/EmailTemplateForm.js.map +1 -1
  69. package/dist/elements/public/EmailTemplateForm/types.d.ts +14 -0
  70. package/dist/elements/public/EmailTemplateForm/types.js.map +1 -1
  71. package/dist/elements/public/TemplateConfigForm/CountriesList.js +11 -2
  72. package/dist/elements/public/TemplateConfigForm/CountriesList.js.map +1 -1
  73. package/dist/elements/public/TemplateConfigForm/CountryCard.d.ts +2 -0
  74. package/dist/elements/public/TemplateConfigForm/CountryCard.js +36 -4
  75. package/dist/elements/public/TemplateConfigForm/CountryCard.js.map +1 -1
  76. package/dist/elements/public/TemplateConfigForm/TemplateConfigForm.d.ts +64 -1
  77. package/dist/elements/public/TemplateConfigForm/TemplateConfigForm.js +814 -28
  78. package/dist/elements/public/TemplateConfigForm/TemplateConfigForm.js.map +1 -1
  79. package/dist/elements/public/TemplateConfigForm/types.d.ts +35 -1
  80. package/dist/elements/public/TemplateConfigForm/types.js.map +1 -1
  81. package/dist/elements/public/TemplateForm/TemplateForm.d.ts +2 -1
  82. package/dist/elements/public/TemplateForm/TemplateForm.js +72 -53
  83. package/dist/elements/public/TemplateForm/TemplateForm.js.map +1 -1
  84. package/dist/elements/public/TemplateForm/types.d.ts +14 -0
  85. package/dist/elements/public/TemplateForm/types.js.map +1 -1
  86. package/dist/mixins/themeable.js +43 -8
  87. package/dist/mixins/themeable.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/cdn/shared-1b7e65e4.js +0 -1
  90. package/dist/cdn/shared-b28a59de.js +0 -1
  91. package/dist/cdn/shared-d94ffc2b.js +0 -1
@@ -1,3 +1,4 @@
1
+ import * as logos from "../PaymentMethodCard/logos.js";
1
2
  import { ChoiceChangeEvent } from "../../private/events.js";
2
3
  import { ScopedElementsMixin } from '@open-wc/scoped-elements';
3
4
  import { html } from 'lit-html';
@@ -15,10 +16,64 @@ import { getDefaultJSON } from "./defaults.js";
15
16
  import { live } from 'lit-html/directives/live';
16
17
  const NS = 'template-config-form';
17
18
  const Base = ScopedElementsMixin(ResponsiveMixin(ConfigurableMixin(ThemeableMixin(TranslatableMixin(NucleonElement, NS)))));
19
+ /**
20
+ * Form element for creating or editing template configs (`fx:template_config`).
21
+ *
22
+ * @slot cart-type:before
23
+ * @slot cart-type:after
24
+ *
25
+ * @slot foxycomplete:before
26
+ * @slot foxycomplete:after
27
+ *
28
+ * @slot locations:before
29
+ * @slot locations:after
30
+ *
31
+ * @slot hidden-fields:before
32
+ * @slot hidden-fields:after
33
+ *
34
+ * @slot cards:before
35
+ * @slot cards:after
36
+ *
37
+ * @slot checkout-type:before
38
+ * @slot checkout-type:after
39
+ *
40
+ * @slot consent:before
41
+ * @slot consent:after
42
+ *
43
+ * @slot fields:before
44
+ * @slot fields:after
45
+ *
46
+ * @slot google-analytics:before
47
+ * @slot google-analytics:after
48
+ *
49
+ * @slot segment-io:before
50
+ * @slot segment-io:after
51
+ *
52
+ * @slot troubleshooting:before
53
+ * @slot troubleshooting:after
54
+ *
55
+ * @slot custom-config:before
56
+ * @slot custom-config:after
57
+ *
58
+ * @slot header:before
59
+ * @slot header:after
60
+ *
61
+ * @slot custom-fields:before
62
+ * @slot custom-fields:after
63
+ *
64
+ * @slot footer:before
65
+ * @slot footer:after
66
+ *
67
+ * @element foxy-template-config-form
68
+ * @since 1.14.0
69
+ */
18
70
  export class TemplateConfigForm extends Base {
19
71
  constructor() {
20
72
  super(...arguments);
73
+ this.templates = {};
74
+ /** URI of the `fx:countries` hAPI resource. */
21
75
  this.countries = '';
76
+ /** URI of the `fx:regions` hAPI resource. */
22
77
  this.regions = '';
23
78
  this.__addHiddenFieldInputValue = '';
24
79
  }
@@ -60,7 +115,16 @@ export class TemplateConfigForm extends Base {
60
115
  ${hidden.matches('foxycomplete', true) ? '' : this.__renderFoxycomplete(json)}
61
116
  ${hidden.matches('locations', true) ? '' : this.__renderLocations(json)}
62
117
  ${hidden.matches('hidden-fields', true) ? '' : this.__renderHiddenFields(json)}
118
+ ${hidden.matches('cards', true) ? '' : this.__renderCards(json)}
119
+ ${hidden.matches('checkout-type', true) ? '' : this.__renderCheckoutType(json)}
120
+ ${hidden.matches('consent', true) ? '' : this.__renderConsent(json)}
121
+ ${hidden.matches('fields', true) ? '' : this.__renderFields(json)}
122
+ ${hidden.matches('google-analytics', true) ? '' : this.__renderGoogleAnalytics(json)}
123
+ ${hidden.matches('segment-io', true) ? '' : this.__renderSegmentIo(json)}
124
+ ${hidden.matches('troubleshooting', true) ? '' : this.__renderTroubleshooting(json)}
125
+ ${hidden.matches('custom-config', true) ? '' : this.__renderCustomConfig(json)}
63
126
  ${hidden.matches('header', true) ? '' : this.__renderHeader(json)}
127
+ ${hidden.matches('custom-fields', true) ? '' : this.__renderCustomFields(json)}
64
128
  ${hidden.matches('footer', true) ? '' : this.__renderFooter(json)}
65
129
  </div>
66
130
 
@@ -85,17 +149,25 @@ export class TemplateConfigForm extends Base {
85
149
  __renderCartType(json) {
86
150
  const { lang, ns } = this;
87
151
  const items = ['default', 'fullpage', 'custom'];
152
+ const isDisabled = this.disabledSelector.matches('cart-type', true);
88
153
  return html `
89
154
  <div>
90
155
  ${this.renderTemplateOrSlot('cart-type:before')}
91
156
 
92
157
  <x-group frame>
93
- <foxy-i18n slot="header" lang=${lang} key="cart_type" ns=${ns}></foxy-i18n>
158
+ <foxy-i18n
159
+ class=${isDisabled ? 'text-disabled' : ''}
160
+ slot="header"
161
+ lang=${lang}
162
+ key="cart_type"
163
+ ns=${ns}
164
+ >
165
+ </foxy-i18n>
94
166
 
95
167
  <x-choice
96
168
  .value=${json.cart_type}
97
169
  .items=${items}
98
- ?disabled=${this.disabledSelector.matches('cart-type', true)}
170
+ ?disabled=${isDisabled}
99
171
  ?readonly=${this.readonlySelector.matches('cart-type', true)}
100
172
  @change=${(evt) => {
101
173
  this.edit({ json: JSON.stringify({ ...json, cart_type: evt.detail }) });
@@ -106,7 +178,7 @@ export class TemplateConfigForm extends Base {
106
178
  <div slot="${item}-label" class="grid leading-s py-s">
107
179
  <foxy-i18n lang=${lang} key="cart_type_${item}" ns=${ns}></foxy-i18n>
108
180
  <foxy-i18n
109
- class="text-tertiary text-s"
181
+ class="text-xs ${isDisabled ? 'text-disabled' : 'text-secondary'}"
110
182
  lang=${lang}
111
183
  key="cart_type_${item}_explainer"
112
184
  ns=${ns}
@@ -153,7 +225,14 @@ export class TemplateConfigForm extends Base {
153
225
  ${this.renderTemplateOrSlot('foxycomplete:before')}
154
226
 
155
227
  <x-group frame>
156
- <foxy-i18n slot="header" lang=${lang} key="foxycomplete" ns=${ns}></foxy-i18n>
228
+ <foxy-i18n
229
+ class=${isDisabled ? 'text-disabled' : ''}
230
+ slot="header"
231
+ lang=${lang}
232
+ key="foxycomplete"
233
+ ns=${ns}
234
+ >
235
+ </foxy-i18n>
157
236
 
158
237
  <x-choice
159
238
  .value=${value}
@@ -176,7 +255,7 @@ export class TemplateConfigForm extends Base {
176
255
  <div slot="${item}-label" class="grid leading-s py-s">
177
256
  <foxy-i18n lang=${lang} key="foxycomplete_${item}" ns=${ns}></foxy-i18n>
178
257
  <foxy-i18n
179
- class="text-tertiary text-s"
258
+ class="text-xs ${isDisabled ? 'text-disabled' : 'text-secondary'}"
180
259
  lang=${lang}
181
260
  key="foxycomplete_${item}_explainer"
182
261
  ns=${ns}
@@ -242,6 +321,8 @@ export class TemplateConfigForm extends Base {
242
321
  __renderLocations(json) {
243
322
  const { lang, ns } = this;
244
323
  const config = json.location_filtering;
324
+ const isDisabled = this.disabledSelector.matches('locations', true);
325
+ const isReadonly = this.readonlySelector.matches('locations', true);
245
326
  const shippingChoice = config.shipping_filter_type === 'blacklist' ? 'block' : 'allow';
246
327
  const billingChoice = config.usage === 'both'
247
328
  ? 'copy'
@@ -274,16 +355,31 @@ export class TemplateConfigForm extends Base {
274
355
  ${this.renderTemplateOrSlot('locations:before')}
275
356
 
276
357
  <x-group frame>
277
- <foxy-i18n slot="header" lang=${lang} key="location_plural" ns=${ns}></foxy-i18n>
358
+ <foxy-i18n
359
+ class=${isDisabled ? 'text-disabled' : ''}
360
+ slot="header"
361
+ lang=${lang}
362
+ key="location_plural"
363
+ ns=${ns}
364
+ >
365
+ </foxy-i18n>
278
366
 
279
367
  <div class="grid sm-grid-cols-2 bg-contrast-10" style="gap: 1px">
280
368
  <x-group class="bg-base pt-m">
281
- <foxy-i18n class="text-tertiary" slot="header" lang=${lang} key="shipping" ns=${ns}>
369
+ <foxy-i18n
370
+ class=${isDisabled ? 'text-disabled' : 'text-tertiary'}
371
+ slot="header"
372
+ lang=${lang}
373
+ key="shipping"
374
+ ns=${ns}
375
+ >
282
376
  </foxy-i18n>
283
377
 
284
378
  <x-choice
285
379
  .items=${['allow', 'block']}
286
380
  .value=${shippingChoice}
381
+ ?disabled=${isDisabled}
382
+ ?readonly=${isReadonly}
287
383
  @change=${(evt) => {
288
384
  if (config.usage !== 'both')
289
385
  config.usage = 'independent';
@@ -303,8 +399,8 @@ export class TemplateConfigForm extends Base {
303
399
  slot=${shippingChoice}
304
400
  lang=${lang}
305
401
  ns=${ns}
306
- ?disabled=${this.disabledSelector.matches('locations', true)}
307
- ?readonly=${this.readonlySelector.matches('locations', true)}
402
+ ?disabled=${isDisabled}
403
+ ?readonly=${isReadonly}
308
404
  @update:countries=${(evt) => {
309
405
  config.shipping_filter_values = evt.currentTarget.countries;
310
406
  normalize();
@@ -316,12 +412,20 @@ export class TemplateConfigForm extends Base {
316
412
  </x-group>
317
413
 
318
414
  <x-group class="bg-base pt-m">
319
- <foxy-i18n class="text-tertiary" slot="header" lang=${lang} key="billing" ns=${ns}>
415
+ <foxy-i18n
416
+ class=${isDisabled ? 'text-disabled' : 'text-tertiary'}
417
+ slot="header"
418
+ lang=${lang}
419
+ key="billing"
420
+ ns=${ns}
421
+ >
320
422
  </foxy-i18n>
321
423
 
322
424
  <x-choice
323
425
  .items=${['allow', 'block', 'copy']}
324
426
  .value=${billingChoice}
427
+ ?disabled=${isDisabled}
428
+ ?readonly=${isReadonly}
325
429
  @change=${(evt) => {
326
430
  if (evt.detail === 'copy') {
327
431
  config.usage = 'both';
@@ -346,8 +450,8 @@ export class TemplateConfigForm extends Base {
346
450
  slot=${billingChoice}
347
451
  lang=${lang}
348
452
  ns=${ns}
349
- ?disabled=${this.disabledSelector.matches('locations', true)}
350
- ?readonly=${this.readonlySelector.matches('locations', true)}
453
+ ?disabled=${isDisabled}
454
+ ?readonly=${isReadonly}
351
455
  ?hidden=${billingChoice === 'copy'}
352
456
  @update:countries=${(evt) => {
353
457
  config.billing_filter_values = evt.currentTarget.countries;
@@ -369,6 +473,8 @@ export class TemplateConfigForm extends Base {
369
473
  const suggestions = [];
370
474
  const fields = [];
371
475
  const config = json.cart_display_config;
476
+ const isDisabled = this.disabledSelector.matches('hidden-fields', true);
477
+ const isReadonly = this.readonlySelector.matches('hidden-fields', true);
372
478
  for (const key in config) {
373
479
  if (!key.startsWith('show_'))
374
480
  continue;
@@ -398,31 +504,54 @@ export class TemplateConfigForm extends Base {
398
504
  ${this.renderTemplateOrSlot('hidden-fields:before')}
399
505
 
400
506
  <x-group frame>
401
- <foxy-i18n slot="header" lang=${lang} key="hidden_fields" ns=${ns}></foxy-i18n>
507
+ <foxy-i18n
508
+ class=${isDisabled ? 'text-disabled' : ''}
509
+ slot="header"
510
+ lang=${lang}
511
+ key="hidden_fields"
512
+ ns=${ns}
513
+ >
514
+ </foxy-i18n>
402
515
 
403
516
  <div class="divide-y divide-contrast-10">
404
- ${fields.map(field => html `
405
- <div class="h-m ml-m pr-xs flex items-center justify-between">
517
+ ${fields.map(field => {
518
+ return html `
519
+ <div
520
+ class=${classMap({
521
+ 'h-m ml-m pr-xs flex items-center justify-between': true,
522
+ 'text-secondary': isReadonly,
523
+ 'text-disabled': isDisabled,
524
+ })}
525
+ >
406
526
  ${suggestions.includes(field)
407
- ? html `<foxy-i18n lang=${lang} key=${field} ns=${ns}></foxy-i18n>`
408
- : html `<span>${field}</span>`}
527
+ ? html `<foxy-i18n lang=${lang} key=${field} ns=${ns}></foxy-i18n>`
528
+ : html `<span>${field}</span>`}
409
529
 
410
530
  <button
411
- class="flex w-xs h-xs items-center justify-center rounded-full transition-colors hover-bg-error-10 hover-text-error focus-outline-none focus-ring-2 focus-ring-inset focus-ring-error-50"
531
+ class=${classMap({
532
+ 'w-xs h-xs rounded-full transition-colors': true,
533
+ 'hover-bg-error-10 hover-text-error': !isDisabled,
534
+ 'focus-outline-none focus-ring-2 ring-inset ring-error-50': !isDisabled,
535
+ 'cursor-default': isDisabled,
536
+ 'flex': !isReadonly,
537
+ 'hidden': isReadonly,
538
+ })}
539
+ ?disabled=${isDisabled}
412
540
  @click=${() => {
413
- if (typeof config[`show_${field}`] === 'boolean') {
414
- config[`show_${field}`] = true;
415
- }
416
- else {
417
- config.hidden_product_options = config.hidden_product_options.filter(option => option !== field);
418
- }
419
- this.edit({ json: JSON.stringify(json) });
420
- }}
541
+ if (typeof config[`show_${field}`] === 'boolean') {
542
+ config[`show_${field}`] = true;
543
+ }
544
+ else {
545
+ config.hidden_product_options = config.hidden_product_options.filter(option => option !== field);
546
+ }
547
+ this.edit({ json: JSON.stringify(json) });
548
+ }}
421
549
  >
422
- <iron-icon icon="icons:close" class="icon-inline text-m"></iron-icon>
550
+ <iron-icon icon="icons:close" class="icon-inline text-m m-auto"></iron-icon>
423
551
  </button>
424
552
  </div>
425
- `)}
553
+ `;
554
+ })}
426
555
  </div>
427
556
 
428
557
  <div
@@ -430,6 +559,8 @@ export class TemplateConfigForm extends Base {
430
559
  class=${classMap({
431
560
  'h-m flex items-center ring-inset ring-primary-50 focus-within-ring-2': true,
432
561
  'border-t border-contrast-10': fields.length > 0,
562
+ 'flex': !isReadonly,
563
+ 'hidden': isReadonly,
433
564
  })}
434
565
  >
435
566
  <input
@@ -437,6 +568,7 @@ export class TemplateConfigForm extends Base {
437
568
  class="w-full bg-transparent appearance-none h-m px-m focus-outline-none"
438
569
  list="hidden-fields-list"
439
570
  .value=${live(this.__addHiddenFieldInputValue)}
571
+ ?disabled=${isDisabled}
440
572
  @keydown=${(evt) => evt.key === 'Enter' && addField()}
441
573
  @input=${(evt) => {
442
574
  this.__addHiddenFieldInputValue = evt.currentTarget.value;
@@ -471,6 +603,636 @@ export class TemplateConfigForm extends Base {
471
603
  </div>
472
604
  `;
473
605
  }
606
+ __renderCards(json) {
607
+ const { lang, ns } = this;
608
+ const isDisabled = this.disabledSelector.matches('cards', true);
609
+ const isReadonly = this.readonlySelector.matches('cards', true);
610
+ const config = json.supported_payment_cards;
611
+ let skipForSaved;
612
+ let skipForSSO;
613
+ if (json.csc_requirements === 'all_cards') {
614
+ skipForSaved = false;
615
+ skipForSSO = false;
616
+ }
617
+ else if (json.csc_requirements === 'sso_only') {
618
+ skipForSaved = true;
619
+ skipForSSO = false;
620
+ }
621
+ else {
622
+ skipForSaved = true;
623
+ skipForSSO = true;
624
+ }
625
+ const typeToName = {
626
+ amex: 'American Express',
627
+ diners: 'Diners Club',
628
+ discover: 'Discover',
629
+ jcb: 'JCB',
630
+ maestro: 'Maestro',
631
+ mastercard: 'Mastercard',
632
+ unionpay: 'UnionPay',
633
+ visa: 'Visa',
634
+ };
635
+ return html `
636
+ <div>
637
+ ${this.renderTemplateOrSlot('cards:before')}
638
+
639
+ <div class="space-y-xs">
640
+ <x-group frame>
641
+ <foxy-i18n
642
+ class=${isDisabled ? 'text-disabled' : ''}
643
+ slot="header"
644
+ lang=${lang}
645
+ key="supported_cards"
646
+ ns=${ns}
647
+ >
648
+ </foxy-i18n>
649
+
650
+ <div class="flex flex-wrap m-xs p-s">
651
+ ${Object.entries(logos).map(([type, logo]) => {
652
+ if (!typeToName[type])
653
+ return;
654
+ const isChecked = config.includes(type);
655
+ return html `
656
+ <div
657
+ class=${classMap({
658
+ 'm-xs rounded': true,
659
+ 'opacity-50 cursor-default': isDisabled,
660
+ 'cursor-pointer ring-primary-50 focus-within-ring-2': !isDisabled,
661
+ })}
662
+ >
663
+ <label
664
+ class=${classMap({
665
+ 'overflow-hidden transition-colors flex rounded border': true,
666
+ 'border-primary bg-primary-10 text-primary': isChecked && !isReadonly,
667
+ 'border-contrast bg-contrast-5 text-secondary': isChecked && isReadonly,
668
+ 'hover-text-body': isChecked && !isDisabled && !isReadonly,
669
+ 'border-contrast-10': !isChecked,
670
+ 'hover-border-primary': !isChecked && !isDisabled && !isReadonly,
671
+ 'hover-text-primary': !isChecked && !isDisabled && !isReadonly,
672
+ })}
673
+ >
674
+ <div class="h-s">${logo}</div>
675
+
676
+ <div class="text-s font-medium mx-s my-auto leading-none">
677
+ ${typeToName[type]}
678
+ </div>
679
+
680
+ <input
681
+ type="checkbox"
682
+ class="sr-only"
683
+ ?disabled=${isDisabled}
684
+ ?checked=${isChecked}
685
+ @change=${(evt) => {
686
+ if (isReadonly)
687
+ return evt.preventDefault();
688
+ evt.stopPropagation();
689
+ if (isChecked) {
690
+ config.splice(config.indexOf(type), 1);
691
+ }
692
+ else {
693
+ config.push(type);
694
+ }
695
+ this.edit({ json: JSON.stringify(json) });
696
+ }}
697
+ />
698
+ </label>
699
+ </div>
700
+ `;
701
+ })}
702
+ </div>
703
+
704
+ <div class="flex flex-wrap p-s border-t border-contrast-10">
705
+ <x-checkbox
706
+ class="m-s"
707
+ ?disabled=${isDisabled || json.csc_requirements === 'new_cards_only'}
708
+ ?readonly=${isReadonly}
709
+ ?checked=${skipForSaved}
710
+ @change=${(evt) => {
711
+ json.csc_requirements = evt.detail ? 'sso_only' : 'all_cards';
712
+ this.edit({ json: JSON.stringify(json) });
713
+ }}
714
+ >
715
+ <foxy-i18n class="leading-s block" lang=${lang} key="skip_csc_for_saved" ns=${ns}>
716
+ </foxy-i18n>
717
+ </x-checkbox>
718
+
719
+ <x-checkbox
720
+ class="m-s"
721
+ ?disabled=${isDisabled}
722
+ ?readonly=${isReadonly}
723
+ ?checked=${skipForSSO}
724
+ @change=${(evt) => {
725
+ json.csc_requirements = evt.detail
726
+ ? 'new_cards_only'
727
+ : skipForSaved
728
+ ? 'sso_only'
729
+ : 'all_cards';
730
+ this.edit({ json: JSON.stringify(json) });
731
+ }}
732
+ >
733
+ <foxy-i18n class="leading-s block" lang=${lang} key="skip_csc_for_sso" ns=${ns}>
734
+ </foxy-i18n>
735
+ </x-checkbox>
736
+ </div>
737
+ </x-group>
738
+
739
+ <foxy-i18n
740
+ class="text-xs leading-s block ${isDisabled ? 'text-disabled' : 'text-secondary'}"
741
+ lang=${lang}
742
+ key="supported_cards_disclaimer"
743
+ ns=${ns}
744
+ >
745
+ </foxy-i18n>
746
+ </div>
747
+
748
+ ${this.renderTemplateOrSlot('cards:after')}
749
+ </div>
750
+ `;
751
+ }
752
+ __renderCheckoutType(json) {
753
+ const { lang, ns } = this;
754
+ const isDisabled = this.disabledSelector.matches('checkout-type', true);
755
+ const isReadonly = this.readonlySelector.matches('checkout-type', true);
756
+ return html `
757
+ <div>
758
+ ${this.renderTemplateOrSlot('checkout-type:before')}
759
+
760
+ <div class="space-y-xs">
761
+ <x-group frame>
762
+ <foxy-i18n
763
+ class=${isDisabled ? 'text-disabled' : ''}
764
+ slot="header"
765
+ lang=${lang}
766
+ key="checkout_type"
767
+ ns=${ns}
768
+ >
769
+ </foxy-i18n>
770
+
771
+ <x-choice
772
+ ?disabled=${isDisabled}
773
+ ?readonly=${isReadonly}
774
+ .items=${['default_account', 'default_guest', 'guest_only', 'account_only']}
775
+ .value=${json.checkout_type}
776
+ .getText=${(item) => this.t(`checkout_type_${item}`)}
777
+ @change=${(evt) => {
778
+ json.checkout_type = evt.detail;
779
+ this.edit({ json: JSON.stringify(json) });
780
+ }}
781
+ >
782
+ </x-choice>
783
+ </x-group>
784
+
785
+ <foxy-i18n
786
+ class="text-xs leading-s block ${isDisabled ? 'text-disabled' : 'text-secondary'}"
787
+ lang=${lang}
788
+ key="checkout_type_helper_text"
789
+ ns=${ns}
790
+ >
791
+ </foxy-i18n>
792
+ </div>
793
+
794
+ ${this.renderTemplateOrSlot('checkout-type:after')}
795
+ </div>
796
+ `;
797
+ }
798
+ __renderConsent(json) {
799
+ const { lang, ns } = this;
800
+ const tosConfig = json.tos_checkbox_settings;
801
+ const mailConfig = json.newsletter_subscribe;
802
+ const sdtaConfig = json.eu_secure_data_transfer_consent;
803
+ const isDisabled = this.disabledSelector.matches('consent', true);
804
+ const isReadonly = this.readonlySelector.matches('consent', true);
805
+ const dividerStyle = 'margin-left: calc(1.125rem + (var(--lumo-space-m) * 2))';
806
+ return html `
807
+ <div>
808
+ ${this.renderTemplateOrSlot('consent:before')}
809
+
810
+ <x-group frame>
811
+ <foxy-i18n
812
+ class=${isDisabled ? 'text-disabled' : ''}
813
+ slot="header"
814
+ lang=${lang}
815
+ key="consent"
816
+ ns=${ns}
817
+ >
818
+ </foxy-i18n>
819
+
820
+ <x-checkbox
821
+ ?disabled=${isDisabled}
822
+ ?readonly=${isReadonly}
823
+ ?checked=${tosConfig.usage === 'required' || tosConfig.usage === 'optional'}
824
+ class="m-m"
825
+ @change=${(evt) => {
826
+ tosConfig.initial_state = evt.detail ? tosConfig.initial_state : 'unchecked';
827
+ tosConfig.is_hidden = false;
828
+ tosConfig.usage = evt.detail ? 'required' : 'none';
829
+ tosConfig.url = evt.detail ? tosConfig.url : '';
830
+ this.edit({ json: JSON.stringify(json) });
831
+ }}
832
+ >
833
+ <div class="flex flex-col">
834
+ <foxy-i18n lang=${lang} key="display_tos_link" ns=${ns}></foxy-i18n>
835
+ <foxy-i18n
836
+ class="text-xs leading-s ${isDisabled ? 'text-disabled' : 'text-secondary'}"
837
+ lang=${lang}
838
+ key="display_tos_link_explainer"
839
+ ns=${ns}
840
+ >
841
+ </foxy-i18n>
842
+ </div>
843
+
844
+ <div slot="content" ?hidden=${tosConfig.usage === 'none'}>
845
+ <vaadin-text-field
846
+ label=${this.t('location_url')}
847
+ class="w-full mt-m"
848
+ placeholder="https://example.com/path/to/tos"
849
+ clear-button-visible
850
+ ?disabled=${isDisabled}
851
+ ?readonly=${isReadonly}
852
+ .value=${tosConfig.url}
853
+ @input=${(evt) => {
854
+ tosConfig.url = evt.currentTarget.value;
855
+ this.edit({ json: JSON.stringify(json) });
856
+ }}
857
+ >
858
+ </vaadin-text-field>
859
+
860
+ <div class="flex flex-wrap -mx-s -mb-s mt-s">
861
+ <x-checkbox
862
+ class="m-s"
863
+ ?disabled=${isDisabled}
864
+ ?checked=${tosConfig.usage === 'required'}
865
+ @change=${(evt) => {
866
+ tosConfig.usage = evt.detail ? 'required' : 'optional';
867
+ this.edit({ json: JSON.stringify(json) });
868
+ }}
869
+ >
870
+ <foxy-i18n class="leading-s block" lang=${lang} key="require_consent" ns=${ns}>
871
+ </foxy-i18n>
872
+ </x-checkbox>
873
+
874
+ <x-checkbox
875
+ class="m-s"
876
+ ?disabled=${isDisabled}
877
+ ?checked=${tosConfig.initial_state === 'checked'}
878
+ @change=${(evt) => {
879
+ tosConfig.initial_state = evt.detail ? 'checked' : 'unchecked';
880
+ this.edit({ json: JSON.stringify(json) });
881
+ }}
882
+ >
883
+ <foxy-i18n class="leading-s block" lang=${lang} key="checked_by_default" ns=${ns}>
884
+ </foxy-i18n>
885
+ </x-checkbox>
886
+ </div>
887
+ </div>
888
+ </x-checkbox>
889
+
890
+ <div style=${dividerStyle} class="border-b border-contrast-10"></div>
891
+
892
+ <x-checkbox
893
+ ?disabled=${isDisabled}
894
+ ?readonly=${isReadonly}
895
+ ?checked=${mailConfig.usage === 'required'}
896
+ class="m-m"
897
+ @change=${(evt) => {
898
+ mailConfig.usage = evt.detail ? 'required' : 'none';
899
+ this.edit({ json: JSON.stringify(json) });
900
+ }}
901
+ >
902
+ <div class="flex flex-col">
903
+ <foxy-i18n lang=${lang} key="newsletter_subscribe" ns=${ns}></foxy-i18n>
904
+ <foxy-i18n
905
+ class="text-xs leading-s ${isDisabled ? 'text-disabled' : 'text-secondary'}"
906
+ lang=${lang}
907
+ key="newsletter_subscribe_explainer"
908
+ ns=${ns}
909
+ >
910
+ </foxy-i18n>
911
+ </div>
912
+ </x-checkbox>
913
+
914
+ <div style=${dividerStyle} class="border-b border-contrast-10"></div>
915
+
916
+ <x-checkbox
917
+ ?disabled=${isDisabled}
918
+ ?readonly=${isReadonly}
919
+ ?checked=${sdtaConfig.usage === 'required'}
920
+ class="m-m"
921
+ @change=${(evt) => {
922
+ sdtaConfig.usage = evt.detail ? 'required' : 'none';
923
+ this.edit({ json: JSON.stringify(json) });
924
+ }}
925
+ >
926
+ <div class="flex flex-col">
927
+ <foxy-i18n lang=${lang} key="display_sdta" ns=${ns}></foxy-i18n>
928
+ <foxy-i18n
929
+ class="text-xs leading-s ${isDisabled ? 'text-disabled' : 'text-secondary'}"
930
+ lang=${lang}
931
+ key="display_sdta_explainer"
932
+ ns=${ns}
933
+ >
934
+ </foxy-i18n>
935
+ </div>
936
+ </x-checkbox>
937
+ </x-group>
938
+
939
+ ${this.renderTemplateOrSlot('consent:before')}
940
+ </div>
941
+ `;
942
+ }
943
+ __renderFields(json) {
944
+ const { lang, ns } = this;
945
+ const isDisabled = this.disabledSelector.matches('fields', true);
946
+ const isReadonly = this.readonlySelector.matches('fields', true);
947
+ const config = json.custom_checkout_field_requirements;
948
+ const options = {
949
+ cart_controls: ['enabled', 'disabled'],
950
+ coupon_entry: ['enabled', 'disabled'],
951
+ billing_first_name: ['default', 'optional', 'required', 'hidden'],
952
+ billing_last_name: ['default', 'optional', 'required', 'hidden'],
953
+ billing_company: ['default', 'optional', 'required', 'hidden'],
954
+ billing_tax_id: ['default', 'optional', 'required', 'hidden'],
955
+ billing_phone: ['default', 'optional', 'required', 'hidden'],
956
+ billing_address1: ['default', 'optional', 'required', 'hidden'],
957
+ billing_address2: ['default', 'optional', 'required', 'hidden'],
958
+ billing_city: ['default', 'optional', 'required', 'hidden'],
959
+ billing_region: ['default', 'optional', 'required', 'hidden'],
960
+ billing_postal_code: ['default', 'optional', 'required', 'hidden'],
961
+ billing_country: ['default', 'optional', 'required', 'hidden'],
962
+ };
963
+ return html `
964
+ <div>
965
+ ${this.renderTemplateOrSlot('fields:before')}
966
+
967
+ <x-group frame>
968
+ <foxy-i18n
969
+ class=${isDisabled ? 'text-disabled' : ''}
970
+ slot="header"
971
+ lang=${lang}
972
+ key="field_plural"
973
+ ns=${ns}
974
+ >
975
+ </foxy-i18n>
976
+
977
+ <div class="bg-contrast-10 grid grid-cols-1 md-grid-cols-2" style="gap: 1px">
978
+ ${Object.entries(options).map(([property, values]) => {
979
+ return html `
980
+ <label
981
+ class=${classMap({
982
+ 'flex items-center pl-m bg-base': true,
983
+ 'text-secondary': isReadonly,
984
+ 'text-disabled': isDisabled,
985
+ })}
986
+ >
987
+ <foxy-i18n
988
+ class="flex-1"
989
+ lang=${lang}
990
+ key=${property.replace('billing_', '')}
991
+ ns=${ns}
992
+ >
993
+ </foxy-i18n>
994
+
995
+ <div
996
+ class=${classMap({
997
+ 'flex items-center text-right font-medium h-s px-s m-xs': isReadonly,
998
+ 'hidden': !isReadonly,
999
+ })}
1000
+ >
1001
+ ${this.t(values.find(value => config[property] === value))}
1002
+ </div>
1003
+
1004
+ <div
1005
+ class=${classMap({
1006
+ 'px-s m-xs flex items-center rounded leading-none': true,
1007
+ 'ring-primary-50 ring-inset focus-within-ring-2': !isDisabled,
1008
+ 'hover-text-primary': !isDisabled,
1009
+ 'cursor-pointer': !isDisabled,
1010
+ 'cursor-default': isDisabled,
1011
+ 'flex': !isReadonly,
1012
+ 'hidden': isReadonly,
1013
+ })}
1014
+ >
1015
+ <select
1016
+ class=${classMap({
1017
+ 'h-s mr-xs text-right appearance-none bg-transparent font-medium': true,
1018
+ 'focus-outline-none cursor-pointer': !isDisabled,
1019
+ 'cursor-default': isDisabled,
1020
+ })}
1021
+ ?disabled=${isDisabled}
1022
+ ?readonly=${isReadonly}
1023
+ @change=${(evt) => {
1024
+ const select = evt.currentTarget;
1025
+ const value = select.options[select.options.selectedIndex].value;
1026
+ config[property] = value;
1027
+ this.edit({ json: JSON.stringify(json) });
1028
+ }}
1029
+ >
1030
+ ${values.map(value => {
1031
+ return html `
1032
+ <option
1033
+ value=${value}
1034
+ ?selected=${config[property] === value}
1035
+ >
1036
+ ${this.t(value)}
1037
+ </option>
1038
+ `;
1039
+ })}
1040
+ </select>
1041
+
1042
+ <iron-icon
1043
+ class="pointer-events-none icon-inline text-xl"
1044
+ icon="icons:expand-more"
1045
+ >
1046
+ </iron-icon>
1047
+ </div>
1048
+ </label>
1049
+ `;
1050
+ })}
1051
+
1052
+ <div class="bg-base hidden md-block"></div>
1053
+ </div>
1054
+ </x-group>
1055
+
1056
+ ${this.renderTemplateOrSlot('fields:after')}
1057
+ </div>
1058
+ `;
1059
+ }
1060
+ __renderGoogleAnalytics(json) {
1061
+ const { lang, ns } = this;
1062
+ const config = json.analytics_config;
1063
+ const sioConfig = config.segment_io;
1064
+ const gaConfig = config.google_analytics;
1065
+ const isDisabled = this.disabledSelector.matches('google-analytics', true);
1066
+ const isReadonly = this.readonlySelector.matches('google-analytics', true);
1067
+ return html `
1068
+ <div>
1069
+ ${this.renderTemplateOrSlot('google-analytics:before')}
1070
+
1071
+ <x-group frame>
1072
+ <span class=${isDisabled ? 'text-disabled' : ''} slot="header">Google Analytics</span>
1073
+
1074
+ <div class="p-m space-y-m">
1075
+ <vaadin-text-field
1076
+ class="w-full"
1077
+ label=${this.t('ga_account_id')}
1078
+ placeholder="UA-1234567-1"
1079
+ helper-text=${this.t('ga_account_id_explainer')}
1080
+ .value=${live(gaConfig.account_id)}
1081
+ ?disabled=${isDisabled}
1082
+ ?readonly=${isReadonly}
1083
+ clear-button-visible
1084
+ @keydown=${(evt) => evt.key === 'Enter' && this.submit()}
1085
+ @input=${(evt) => {
1086
+ gaConfig.account_id = evt.currentTarget.value;
1087
+ gaConfig.usage = gaConfig.account_id ? 'required' : 'none';
1088
+ config.usage = gaConfig.account_id || sioConfig.account_id ? 'required' : 'none';
1089
+ }}
1090
+ >
1091
+ </vaadin-text-field>
1092
+
1093
+ <x-checkbox
1094
+ ?disabled=${isDisabled}
1095
+ ?readonly=${isReadonly}
1096
+ ?checked=${gaConfig.include_on_site}
1097
+ @change=${(evt) => {
1098
+ gaConfig.include_on_site = evt.detail;
1099
+ this.edit({ json: JSON.stringify(json) });
1100
+ }}
1101
+ >
1102
+ <div class="flex flex-col">
1103
+ <foxy-i18n lang=${lang} key="ga_include_on_site" ns=${ns}></foxy-i18n>
1104
+ <foxy-i18n
1105
+ class="text-xs leading-s ${isDisabled ? 'text-disabled' : 'text-secondary'}"
1106
+ lang=${lang}
1107
+ key="ga_include_on_site_explainer"
1108
+ ns=${ns}
1109
+ >
1110
+ </foxy-i18n>
1111
+ </div>
1112
+ </x-checkbox>
1113
+ </div>
1114
+ </x-group>
1115
+
1116
+ ${this.renderTemplateOrSlot('google-analytics:after')}
1117
+ </div>
1118
+ `;
1119
+ }
1120
+ __renderSegmentIo(json) {
1121
+ const config = json.analytics_config;
1122
+ const sioConfig = config.segment_io;
1123
+ const gaConfig = config.google_analytics;
1124
+ const isDisabled = this.disabledSelector.matches('segment-io', true);
1125
+ const isReadonly = this.readonlySelector.matches('segment-io', true);
1126
+ return html `
1127
+ <div>
1128
+ ${this.renderTemplateOrSlot('segment-io:before')}
1129
+
1130
+ <x-group frame>
1131
+ <span class=${isDisabled ? 'text-disabled' : ''} slot="header">Segment.io</span>
1132
+
1133
+ <div class="p-m">
1134
+ <vaadin-text-field
1135
+ class="w-full"
1136
+ label=${this.t('sio_account_id')}
1137
+ placeholder="MY-WRITE-KEY"
1138
+ helper-text=${this.t('sio_account_id_explainer')}
1139
+ .value=${live(sioConfig.account_id)}
1140
+ ?disabled=${isDisabled}
1141
+ ?readonly=${isReadonly}
1142
+ clear-button-visible
1143
+ @keydown=${(evt) => evt.key === 'Enter' && this.submit()}
1144
+ @input=${(evt) => {
1145
+ sioConfig.account_id = evt.currentTarget.value;
1146
+ sioConfig.usage = sioConfig.account_id ? 'required' : 'none';
1147
+ config.usage = gaConfig.account_id || sioConfig.account_id ? 'required' : 'none';
1148
+ }}
1149
+ >
1150
+ </vaadin-text-field>
1151
+ </div>
1152
+ </x-group>
1153
+
1154
+ ${this.renderTemplateOrSlot('segment-io:after')}
1155
+ </div>
1156
+ `;
1157
+ }
1158
+ __renderTroubleshooting(json) {
1159
+ const { lang, ns } = this;
1160
+ const config = json.debug;
1161
+ const isDisabled = this.disabledSelector.matches('troubleshooting', true);
1162
+ const isReadonly = this.readonlySelector.matches('troubleshooting', true);
1163
+ return html `
1164
+ <div>
1165
+ ${this.renderTemplateOrSlot('troubleshooting:before')}
1166
+
1167
+ <x-group frame>
1168
+ <foxy-i18n
1169
+ class=${isDisabled ? 'text-disabled' : ''}
1170
+ slot="header"
1171
+ lang=${lang}
1172
+ key="troubleshooting"
1173
+ ns=${ns}
1174
+ >
1175
+ </foxy-i18n>
1176
+
1177
+ <div class="p-m space-y-m">
1178
+ <x-checkbox
1179
+ ?disabled=${isDisabled}
1180
+ ?readonly=${isReadonly}
1181
+ ?checked=${config.usage === 'required'}
1182
+ @change=${(evt) => {
1183
+ config.usage = evt.detail ? 'required' : 'none';
1184
+ this.edit({ json: JSON.stringify(json) });
1185
+ }}
1186
+ >
1187
+ <div class="flex flex-col">
1188
+ <foxy-i18n lang=${lang} key="troubleshooting_debug" ns=${ns}></foxy-i18n>
1189
+ <foxy-i18n
1190
+ class="text-xs leading-s ${isDisabled ? 'text-disabled' : 'text-secondary'}"
1191
+ lang=${lang}
1192
+ key="troubleshooting_debug_explainer"
1193
+ ns=${ns}
1194
+ >
1195
+ </foxy-i18n>
1196
+ </div>
1197
+ </x-checkbox>
1198
+ </div>
1199
+ </x-group>
1200
+
1201
+ ${this.renderTemplateOrSlot('troubleshooting:after')}
1202
+ </div>
1203
+ `;
1204
+ }
1205
+ __renderCustomConfig(json) {
1206
+ return html `
1207
+ <div>
1208
+ ${this.renderTemplateOrSlot('custom-config:before')}
1209
+
1210
+ <vaadin-text-area
1211
+ class="w-full"
1212
+ label=${this.t('custom_config')}
1213
+ placeholder='{ "key": "value" }'
1214
+ helper-text=${this.t('custom_config_helper_text')}
1215
+ .value=${json.custom_config ? JSON.stringify(json.custom_config, null, 2) : ''}
1216
+ ?disabled=${this.disabledSelector.matches('custom-config', true)}
1217
+ ?readonly=${this.readonlySelector.matches('custom-config', true)}
1218
+ @input=${(evt) => {
1219
+ const input = evt.currentTarget;
1220
+ try {
1221
+ json.custom_config = input.value ? JSON.parse(input.value) : '';
1222
+ this.edit({ json: JSON.stringify(json) });
1223
+ input.invalid = false;
1224
+ }
1225
+ catch (_a) {
1226
+ input.invalid = true;
1227
+ }
1228
+ }}
1229
+ >
1230
+ </vaadin-text-area>
1231
+
1232
+ ${this.renderTemplateOrSlot('custom-config:after')}
1233
+ </div>
1234
+ `;
1235
+ }
474
1236
  __renderHeader(json) {
475
1237
  return html `
476
1238
  <div>
@@ -498,6 +1260,30 @@ export class TemplateConfigForm extends Base {
498
1260
  </div>
499
1261
  `;
500
1262
  }
1263
+ __renderCustomFields(json) {
1264
+ return html `
1265
+ <div>
1266
+ ${this.renderTemplateOrSlot('custom-fields:before')}
1267
+
1268
+ <vaadin-text-area
1269
+ class="w-full"
1270
+ label=${this.t('custom_fields')}
1271
+ helper-text=${this.t('custom_fields_helper_text')}
1272
+ .value=${json.custom_script_values.header}
1273
+ ?disabled=${this.disabledSelector.matches('header', true)}
1274
+ ?readonly=${this.readonlySelector.matches('header', true)}
1275
+ @input=${(evt) => {
1276
+ const newValue = evt.currentTarget.value;
1277
+ json.custom_script_values.checkout_fields = newValue;
1278
+ this.edit({ json: JSON.stringify(json) });
1279
+ }}
1280
+ >
1281
+ </vaadin-text-area>
1282
+
1283
+ ${this.renderTemplateOrSlot('custom-fields:after')}
1284
+ </div>
1285
+ `;
1286
+ }
501
1287
  __renderFooter(json) {
502
1288
  return html `
503
1289
  <div>