@elementor/editor-canvas 0.28.0 → 3.32.0-21

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 (31) hide show
  1. package/CHANGELOG.md +0 -20
  2. package/dist/index.js +167 -36
  3. package/dist/index.mjs +163 -32
  4. package/package.json +15 -15
  5. package/src/__tests__/flex-transformer.test.ts +272 -0
  6. package/src/__tests__/styles-prop-resolver.test.ts +78 -63
  7. package/src/components/__tests__/style-renderer.test.tsx +2 -2
  8. package/src/components/style-renderer.tsx +1 -1
  9. package/src/hooks/__tests__/use-style-items.test.ts +106 -7
  10. package/src/hooks/use-style-items.ts +47 -5
  11. package/src/init-settings-transformers.ts +2 -0
  12. package/src/init-style-transformers.ts +10 -0
  13. package/src/renderers/__tests__/__snapshots__/create-styles-renderer.test.ts.snap +2 -0
  14. package/src/renderers/__tests__/create-styles-renderer.test.ts +98 -0
  15. package/src/renderers/create-styles-renderer.ts +26 -2
  16. package/src/style-commands/__tests__/paste-style.test.ts +30 -0
  17. package/src/style-commands/__tests__/reset-style.test.ts +4 -0
  18. package/src/style-commands/undoable-actions/paste-element-style.ts +2 -1
  19. package/src/transformers/settings/attributes-transformer.ts +25 -0
  20. package/src/transformers/styles/background-overlay-transformer.ts +1 -10
  21. package/src/transformers/styles/create-combine-array-transformer.ts +3 -1
  22. package/src/transformers/styles/filter-transformer.ts +10 -18
  23. package/src/transformers/styles/flex-transformer.ts +55 -0
  24. package/src/transformers/styles/transform-move-transformer.ts +3 -1
  25. package/src/transformers/styles/transform-rotate-transformer.ts +19 -0
  26. package/src/transformers/styles/transform-scale-transformer.ts +11 -0
  27. package/src/transformers/styles/transform-skew-transformer.ts +15 -0
  28. package/src/transformers/styles/transition-transformer.ts +25 -0
  29. package/.turbo/turbo-build.log +0 -22
  30. package/dist/index.js.map +0 -1
  31. package/dist/index.mjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,25 +1,5 @@
1
1
  # @elementor/editor-canvas
2
2
 
3
- ## 0.28.0
4
-
5
- ### Minor Changes
6
-
7
- - 64b3e09: Adds support for the backdrop-filter css property to atomic widgets.
8
- - 3904505: Adds hue rotate css filter
9
- - cdbd491: Add Drop Shadow filter
10
-
11
- ### Patch Changes
12
-
13
- - Updated dependencies [64b3e09]
14
- - Updated dependencies [3904505]
15
- - Updated dependencies [cdbd491]
16
- - @elementor/editor-props@0.18.0
17
- - @elementor/editor-styles-repository@0.10.7
18
- - @elementor/editor-elements@0.9.2
19
- - @elementor/editor-styles@0.6.14
20
- - @elementor/editor@0.21.1
21
- - @elementor/editor-notifications@1.4.1
22
-
23
3
  ## 0.27.0
24
4
 
25
5
  ### Minor Changes
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ function useElementsDom() {
174
174
 
175
175
  // src/components/style-renderer.tsx
176
176
  var React3 = __toESM(require("react"));
177
- var import_editor_v1_adapters4 = require("@elementor/editor-v1-adapters");
177
+ var import_editor_v1_adapters5 = require("@elementor/editor-v1-adapters");
178
178
  var import_ui2 = require("@elementor/ui");
179
179
 
180
180
  // src/hooks/use-documents-css-links.ts
@@ -234,8 +234,9 @@ function getLinkAttrs(el) {
234
234
 
235
235
  // src/hooks/use-style-items.ts
236
236
  var import_react8 = require("react");
237
+ var import_editor_responsive2 = require("@elementor/editor-responsive");
237
238
  var import_editor_styles_repository = require("@elementor/editor-styles-repository");
238
- var import_editor_v1_adapters3 = require("@elementor/editor-v1-adapters");
239
+ var import_editor_v1_adapters4 = require("@elementor/editor-v1-adapters");
239
240
 
240
241
  // src/utils/abort-previous-runs.ts
241
242
  function abortPreviousRuns(cb) {
@@ -421,6 +422,10 @@ function useStylePropResolver() {
421
422
  var import_react7 = require("react");
422
423
  var import_editor_responsive = require("@elementor/editor-responsive");
423
424
 
425
+ // src/renderers/create-styles-renderer.ts
426
+ var import_editor_v1_adapters3 = require("@elementor/editor-v1-adapters");
427
+ var import_utils2 = require("@elementor/utils");
428
+
424
429
  // src/renderers/errors.ts
425
430
  var import_utils = require("@elementor/utils");
426
431
  var UnknownStyleTypeError = (0, import_utils.createError)({
@@ -437,11 +442,13 @@ function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
437
442
  const stylesCssPromises = styles.map(async (style) => {
438
443
  const variantCssPromises = Object.values(style.variants).map(async (variant) => {
439
444
  const css = await propsToCss({ props: variant.props, resolve, signal });
440
- return createStyleWrapper().for(style.cssName, style.type).withPrefix(selectorPrefix).withState(variant.meta.state).withMediaQuery(variant.meta.breakpoint ? breakpoints[variant.meta.breakpoint] : null).wrap(css);
445
+ const customCss = customCssToString(variant.custom_css);
446
+ return createStyleWrapper().for(style.cssName, style.type).withPrefix(selectorPrefix).withState(variant.meta.state).withMediaQuery(variant.meta.breakpoint ? breakpoints[variant.meta.breakpoint] : null).wrap(css + customCss);
441
447
  });
442
448
  const variantsCss = await Promise.all(variantCssPromises);
443
449
  return {
444
450
  id: style.id,
451
+ breakpoint: style?.variants[0]?.meta?.breakpoint || "desktop",
445
452
  value: variantsCss.join("")
446
453
  };
447
454
  });
@@ -485,6 +492,16 @@ async function propsToCss({ props, resolve, signal }) {
485
492
  return acc;
486
493
  }, []).join("");
487
494
  }
495
+ function customCssToString(customCss) {
496
+ if (!(0, import_editor_v1_adapters3.isExperimentActive)(import_editor_v1_adapters3.EXPERIMENTAL_FEATURES.CUSTOM_CSS) || !customCss?.raw) {
497
+ return "";
498
+ }
499
+ const decoded = (0, import_utils2.decodeString)(customCss.raw);
500
+ if (!decoded.trim()) {
501
+ return "";
502
+ }
503
+ return decoded + "\n";
504
+ }
488
505
 
489
506
  // src/hooks/use-style-renderer.ts
490
507
  var SELECTOR_PREFIX = ".elementor";
@@ -525,12 +542,20 @@ function useStyleItems() {
525
542
  };
526
543
  }, [providerAndSubscribers]);
527
544
  useOnMount(() => {
528
- (0, import_editor_v1_adapters3.registerDataHook)("after", "editor/documents/attach-preview", async () => {
545
+ (0, import_editor_v1_adapters4.registerDataHook)("after", "editor/documents/attach-preview", async () => {
529
546
  const promises = providerAndSubscribers.map(async ({ subscriber }) => subscriber());
530
547
  await Promise.all(promises);
531
548
  });
532
549
  });
533
- return Object.values(styleItems).sort(({ provider: providerA }, { provider: providerB }) => providerA.priority - providerB.priority).flatMap(({ items }) => items);
550
+ const breakpointsOrder = (0, import_editor_responsive2.getBreakpoints)().map((breakpoint) => breakpoint.id);
551
+ return (0, import_react8.useMemo)(
552
+ () => Object.values(styleItems).sort(({ provider: providerA }, { provider: providerB }) => providerA.priority - providerB.priority).flatMap(({ items }) => items).sort(({ breakpoint: breakpointA }, { breakpoint: breakpointB }) => {
553
+ return breakpointsOrder.indexOf(breakpointA) - breakpointsOrder.indexOf(breakpointB);
554
+ }),
555
+ // eslint-disable-next-line
556
+ // eslint-disable-next-line react-hooks/exhaustive-deps
557
+ [styleItems, breakpointsOrder.join("-")]
558
+ );
534
559
  }
535
560
  function createProviderSubscriber({ provider, renderStyles, setStyleItems }) {
536
561
  return abortPreviousRuns(
@@ -543,7 +568,7 @@ function createProviderSubscriber({ provider, renderStyles, setStyleItems }) {
543
568
  cssName: provider.actions.resolveCssName(style.id)
544
569
  };
545
570
  });
546
- return renderStyles({ styles, signal });
571
+ return renderStyles({ styles: breakToBreakpoints(styles), signal });
547
572
  }).then((items) => {
548
573
  setStyleItems((prev) => ({
549
574
  ...prev,
@@ -551,6 +576,29 @@ function createProviderSubscriber({ provider, renderStyles, setStyleItems }) {
551
576
  }));
552
577
  }).execute()
553
578
  );
579
+ function breakToBreakpoints(styles) {
580
+ return Object.values(
581
+ styles.reduce(
582
+ (acc, style) => {
583
+ style.variants.forEach((variant) => {
584
+ const breakpoint = variant.meta.breakpoint || "desktop";
585
+ if (!acc[style.id]) {
586
+ acc[style.id] = {};
587
+ }
588
+ if (!acc[style.id][breakpoint]) {
589
+ acc[style.id][breakpoint] = {
590
+ ...style,
591
+ variants: []
592
+ };
593
+ }
594
+ acc[style.id][breakpoint].variants.push(variant);
595
+ });
596
+ return acc;
597
+ },
598
+ {}
599
+ )
600
+ ).flatMap((breakpointMap) => Object.values(breakpointMap));
601
+ }
554
602
  }
555
603
 
556
604
  // src/components/style-renderer.tsx
@@ -561,24 +609,43 @@ function StyleRenderer() {
561
609
  if (!container) {
562
610
  return null;
563
611
  }
564
- return /* @__PURE__ */ React3.createElement(import_ui2.Portal, { container }, styleItems.map((item) => /* @__PURE__ */ React3.createElement("style", { "data-e-style-id": item.id, key: item.id }, item.value)), linksAttrs.map((attrs) => /* @__PURE__ */ React3.createElement("link", { ...attrs, key: attrs.id })));
612
+ return /* @__PURE__ */ React3.createElement(import_ui2.Portal, { container }, styleItems.map((item) => /* @__PURE__ */ React3.createElement("style", { "data-e-style-id": item.id, key: `${item.id}-${item.breakpoint}` }, item.value)), linksAttrs.map((attrs) => /* @__PURE__ */ React3.createElement("link", { ...attrs, key: attrs.id })));
565
613
  }
566
614
  function usePortalContainer() {
567
- return (0, import_editor_v1_adapters4.__privateUseListenTo)((0, import_editor_v1_adapters4.commandEndEvent)("editor/documents/attach-preview"), () => getCanvasIframeDocument()?.head);
615
+ return (0, import_editor_v1_adapters5.__privateUseListenTo)((0, import_editor_v1_adapters5.commandEndEvent)("editor/documents/attach-preview"), () => getCanvasIframeDocument()?.head);
568
616
  }
569
617
 
570
618
  // src/settings-transformers-registry.ts
571
619
  var settingsTransformersRegistry = createTransformersRegistry();
572
620
 
573
- // src/transformers/settings/classes-transformer.ts
574
- var import_editor_styles_repository2 = require("@elementor/editor-styles-repository");
575
-
576
621
  // src/transformers/create-transformer.ts
577
622
  function createTransformer(cb) {
578
623
  return cb;
579
624
  }
580
625
 
626
+ // src/transformers/settings/attributes-transformer.ts
627
+ function escapeHtmlAttribute(value) {
628
+ const specialChars = {
629
+ "&": "&",
630
+ "<": "&lt;",
631
+ ">": "&gt;",
632
+ "'": "&#39;",
633
+ '"': "&quot;"
634
+ };
635
+ return value.replace(/[&<>'"]/g, (char) => specialChars[char] || char);
636
+ }
637
+ var attributesTransformer = createTransformer((values) => {
638
+ return values.map((value) => {
639
+ if (!value.key || !value.value) {
640
+ return "";
641
+ }
642
+ const escapedValue = escapeHtmlAttribute(value.value);
643
+ return `${value.key}="${escapedValue}"`;
644
+ }).join(" ");
645
+ });
646
+
581
647
  // src/transformers/settings/classes-transformer.ts
648
+ var import_editor_styles_repository2 = require("@elementor/editor-styles-repository");
582
649
  function createClassesTransformer() {
583
650
  const cache = /* @__PURE__ */ new Map();
584
651
  return createTransformer((value) => {
@@ -648,7 +715,7 @@ var plainTransformer = createTransformer((value) => {
648
715
 
649
716
  // src/init-settings-transformers.ts
650
717
  function initSettingsTransformers() {
651
- settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).registerFallback(plainTransformer);
718
+ settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("key-value-array", attributesTransformer).registerFallback(plainTransformer);
652
719
  }
653
720
 
654
721
  // src/transformers/styles/background-color-overlay-transformer.ts
@@ -691,7 +758,6 @@ var backgroundImageSizeScaleTransformer = createTransformer(
691
758
  );
692
759
 
693
760
  // src/transformers/styles/background-overlay-transformer.ts
694
- var import_editor_v1_adapters5 = require("@elementor/editor-v1-adapters");
695
761
  var backgroundOverlayTransformer = createTransformer(
696
762
  (value) => {
697
763
  if (!value || value.length === 0) {
@@ -716,7 +782,6 @@ var backgroundOverlayTransformer = createTransformer(
716
782
  }
717
783
  );
718
784
  function normalizeOverlayValues(overlays) {
719
- const isVersion330Active = (0, import_editor_v1_adapters5.isExperimentActive)("e_v_3_30");
720
785
  const mappedValues = overlays.map((item) => {
721
786
  if (typeof item === "string") {
722
787
  return {
@@ -729,16 +794,12 @@ function normalizeOverlayValues(overlays) {
729
794
  }
730
795
  return item;
731
796
  });
732
- if (!isVersion330Active) {
733
- return mappedValues;
734
- }
735
797
  return mappedValues.filter((item) => item && !!item.src);
736
798
  }
737
799
  function getValuesString(items, prop, defaultValue, preventUnification = false) {
738
- const isVersion330Active = (0, import_editor_v1_adapters5.isExperimentActive)("e_v_3_30");
739
800
  const isEmpty = items.filter((item) => item?.[prop]).length === 0;
740
801
  if (isEmpty) {
741
- return isVersion330Active ? defaultValue : null;
802
+ return defaultValue;
742
803
  }
743
804
  const formattedValues = items.map((item) => item[prop] ?? defaultValue);
744
805
  if (!preventUnification) {
@@ -766,7 +827,9 @@ var colorStopTransformer = createTransformer(
766
827
 
767
828
  // src/transformers/styles/create-combine-array-transformer.ts
768
829
  var createCombineArrayTransformer = (delimiter) => {
769
- return createTransformer((value) => value.filter(Boolean).join(delimiter));
830
+ return createTransformer(
831
+ (value) => value?.length ? value.filter(Boolean).join(delimiter) : null
832
+ );
770
833
  };
771
834
 
772
835
  // src/transformers/styles/create-multi-props-transformer.ts
@@ -785,23 +848,51 @@ var filterTransformer = createTransformer((filterValues) => {
785
848
  return filterValues.filter(Boolean).map(mapToFilterFunctionString).join(" ");
786
849
  });
787
850
  var mapToFilterFunctionString = (value) => {
788
- if ("radius" in value) {
789
- return value.radius ? `blur(${value.radius})` : "";
790
- }
791
- if ("amount" in value) {
792
- return value.amount ? `brightness(${value.amount})` : "";
793
- }
794
- if ("xAxis" in value && "yAxis" in value && "blur" in value && "color" in value) {
795
- const { xAxis, yAxis, blur, color } = value;
851
+ if (value.func === "drop-shadow") {
852
+ const { xAxis, yAxis, blur, color } = value.args;
796
853
  return `drop-shadow(${xAxis || "0px"} ${yAxis || "0px"} ${blur || "10px"} ${color || "transparent"})`;
797
854
  }
798
- const keys = Object.keys(value);
799
- if (!keys[0]) {
855
+ if (!value.func || !value.args) {
800
856
  return "";
801
857
  }
802
- return value[keys[0]] ? `${keys[0]}(${value[keys[0]]})` : "";
858
+ return `${value.func}(${value.args})`;
803
859
  };
804
860
 
861
+ // src/transformers/styles/flex-transformer.ts
862
+ var flexTransformer = createTransformer((value) => {
863
+ const grow = value.flexGrow;
864
+ const shrink = value.flexShrink;
865
+ const basis = value.flexBasis;
866
+ const hasGrow = grow !== void 0 && grow !== null;
867
+ const hasShrink = shrink !== void 0 && shrink !== null;
868
+ const hasBasis = basis !== void 0 && basis !== null;
869
+ if (!hasGrow && !hasShrink && !hasBasis) {
870
+ return null;
871
+ }
872
+ if (hasGrow && hasShrink && hasBasis) {
873
+ return `${grow} ${shrink} ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
874
+ }
875
+ if (hasGrow && hasShrink && !hasBasis) {
876
+ return `${grow} ${shrink}`;
877
+ }
878
+ if (hasGrow && !hasShrink && hasBasis) {
879
+ return `${grow} 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
880
+ }
881
+ if (!hasGrow && hasShrink && hasBasis) {
882
+ return `0 ${shrink} ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
883
+ }
884
+ if (hasGrow && !hasShrink && !hasBasis) {
885
+ return `${grow}`;
886
+ }
887
+ if (!hasGrow && hasShrink && !hasBasis) {
888
+ return `0 ${shrink}`;
889
+ }
890
+ if (!hasGrow && !hasShrink && hasBasis) {
891
+ return `0 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
892
+ }
893
+ return null;
894
+ });
895
+
805
896
  // src/transformers/styles/position-transformer.ts
806
897
  var positionTransformer = createTransformer(({ x, y }) => `${x ?? "0px"} ${y ?? "0px"}`);
807
898
 
@@ -826,8 +917,33 @@ var strokeTransformer = createTransformer((value) => {
826
917
  });
827
918
 
828
919
  // src/transformers/styles/transform-move-transformer.ts
920
+ var defaultMove = "0px";
829
921
  var transformMoveTransformer = createTransformer((value) => {
830
- return `translate3d(${value.x}, ${value.y}, ${value.z})`;
922
+ return `translate3d(${value.x ?? defaultMove}, ${value.y ?? defaultMove}, ${value.z ?? defaultMove})`;
923
+ });
924
+
925
+ // src/transformers/styles/transform-rotate-transformer.ts
926
+ var defaultRotate = "0deg";
927
+ var transformRotateTransformer = createTransformer((value) => {
928
+ const transforms = [
929
+ `rotateX(${value?.x ?? defaultRotate})`,
930
+ `rotateY(${value?.y ?? defaultRotate})`,
931
+ `rotateZ(${value?.z ?? defaultRotate})`
932
+ ];
933
+ return transforms.join(" ");
934
+ });
935
+
936
+ // src/transformers/styles/transform-scale-transformer.ts
937
+ var transformScaleTransformer = createTransformer((value) => {
938
+ return `scale3d(${value.x ?? 1}, ${value.y ?? 1}, ${value.z ?? 1})`;
939
+ });
940
+
941
+ // src/transformers/styles/transform-skew-transformer.ts
942
+ var defaultSkew = "0deg";
943
+ var transformSkewTransformer = createTransformer((value) => {
944
+ const x = value?.x ?? defaultSkew;
945
+ const y = value?.y ?? defaultSkew;
946
+ return `skew(${x}, ${y})`;
831
947
  });
832
948
 
833
949
  // src/transformers/styles/transform-transformer.ts
@@ -838,6 +954,20 @@ var transformTransformer = createTransformer((values) => {
838
954
  return values.join(" ");
839
955
  });
840
956
 
957
+ // src/transformers/styles/transition-transformer.ts
958
+ var transitionTransformer = createTransformer((transitionValues) => {
959
+ if (transitionValues?.length < 1) {
960
+ return null;
961
+ }
962
+ return transitionValues.filter(Boolean).map(mapToTransitionString).join(", ");
963
+ });
964
+ var mapToTransitionString = (value) => {
965
+ if (!value.selection || !value.size) {
966
+ return "";
967
+ }
968
+ return `${value.selection.value} ${value.size}`;
969
+ };
970
+
841
971
  // src/init-style-transformers.ts
842
972
  function initStyleTransformers() {
843
973
  styleTransformersRegistry.register("size", sizeTransformer).register("shadow", shadowTransformer).register("stroke", strokeTransformer).register(
@@ -846,10 +976,10 @@ function initStyleTransformers() {
846
976
  ["block-start", "block-end", "inline-start", "inline-end"],
847
977
  ({ propKey, key }) => `${propKey}-${key}`
848
978
  )
849
- ).register("filter", filterTransformer).register("backdrop-filter", filterTransformer).register("box-shadow", createCombineArrayTransformer(",")).register("background", backgroundTransformer).register("background-overlay", backgroundOverlayTransformer).register("background-color-overlay", backgroundColorOverlayTransformer).register("background-image-overlay", backgroundImageOverlayTransformer).register("background-gradient-overlay", backgroundGradientOverlayTransformer).register("gradient-color-stop", createCombineArrayTransformer(",")).register("color-stop", colorStopTransformer).register("background-image-position-offset", positionTransformer).register("background-image-size-scale", backgroundImageSizeScaleTransformer).register("image-src", imageSrcTransformer).register("image", imageTransformer).register("object-position", positionTransformer).register("transform-move", transformMoveTransformer).register("transform", transformTransformer).register(
979
+ ).register("filter", filterTransformer).register("backdrop-filter", filterTransformer).register("box-shadow", createCombineArrayTransformer(",")).register("background", backgroundTransformer).register("background-overlay", backgroundOverlayTransformer).register("background-color-overlay", backgroundColorOverlayTransformer).register("background-image-overlay", backgroundImageOverlayTransformer).register("background-gradient-overlay", backgroundGradientOverlayTransformer).register("gradient-color-stop", createCombineArrayTransformer(",")).register("color-stop", colorStopTransformer).register("background-image-position-offset", positionTransformer).register("background-image-size-scale", backgroundImageSizeScaleTransformer).register("image-src", imageSrcTransformer).register("image", imageTransformer).register("object-position", positionTransformer).register("transform-move", transformMoveTransformer).register("transform-scale", transformScaleTransformer).register("transform-rotate", transformRotateTransformer).register("transform-skew", transformSkewTransformer).register("transform", transformTransformer).register("transition", transitionTransformer).register(
850
980
  "layout-direction",
851
981
  createMultiPropsTransformer(["row", "column"], ({ propKey, key }) => `${key}-${propKey}`)
852
- ).register(
982
+ ).register("flex", flexTransformer).register(
853
983
  "border-width",
854
984
  createMultiPropsTransformer(
855
985
  ["block-start", "block-end", "inline-start", "inline-end"],
@@ -1235,12 +1365,13 @@ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters8.undoable)(
1235
1365
  originalStyle
1236
1366
  };
1237
1367
  if (styleId) {
1238
- newStyle.variants.forEach(({ meta, props }) => {
1368
+ newStyle.variants.forEach(({ meta, props, custom_css: customCss }) => {
1239
1369
  (0, import_editor_elements5.updateElementStyle)({
1240
1370
  elementId,
1241
1371
  styleId,
1242
1372
  meta,
1243
- props
1373
+ props,
1374
+ custom_css: customCss
1244
1375
  });
1245
1376
  });
1246
1377
  } else {