@elementor/editor-variables 3.33.0-104 → 3.33.0-106

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.
package/dist/index.js CHANGED
@@ -116,9 +116,66 @@ var apiClient = {
116
116
  payload.value = value;
117
117
  }
118
118
  return (0, import_http_client.httpService)().post(BASE_PATH + "/restore", payload);
119
+ },
120
+ batch: (payload) => {
121
+ return (0, import_http_client.httpService)().post(BASE_PATH + "/batch", payload);
119
122
  }
120
123
  };
121
124
 
125
+ // src/batch-operations.ts
126
+ var isTempId = (id2) => {
127
+ return id2.startsWith("tmp-");
128
+ };
129
+ var buildOperationsArray = (originalVariables, currentVariables) => {
130
+ const operations = [];
131
+ Object.entries(currentVariables).forEach(([id2, variable]) => {
132
+ if (isTempId(id2)) {
133
+ operations.push({
134
+ type: "create",
135
+ variable: {
136
+ ...variable,
137
+ id: id2
138
+ }
139
+ });
140
+ } else if (originalVariables[id2]) {
141
+ const original = originalVariables[id2];
142
+ if (original.deleted && !variable.deleted) {
143
+ operations.push({
144
+ type: "restore",
145
+ id: id2,
146
+ ...original.label !== variable.label && { label: variable.label },
147
+ ...original.value !== variable.value && { value: variable.value }
148
+ });
149
+ } else if (!variable.deleted && (original.label !== variable.label || original.value !== variable.value)) {
150
+ operations.push({
151
+ type: "update",
152
+ id: id2,
153
+ variable: {
154
+ ...original.label !== variable.label && { label: variable.label },
155
+ ...original.value !== variable.value && { value: variable.value }
156
+ }
157
+ });
158
+ }
159
+ }
160
+ });
161
+ Object.entries(originalVariables).forEach(([id2, variable]) => {
162
+ if (isTempId(id2) || variable.deleted) {
163
+ return;
164
+ }
165
+ const currentVariable = currentVariables[id2];
166
+ if (!currentVariable || currentVariable.deleted) {
167
+ operations.push({
168
+ type: "delete",
169
+ id: id2
170
+ });
171
+ }
172
+ });
173
+ return operations.filter((op) => {
174
+ const id2 = op.id || op.variable?.id;
175
+ return id2 && !(isTempId(id2) && currentVariables[id2]?.deleted);
176
+ });
177
+ };
178
+
122
179
  // src/storage.ts
123
180
  var STORAGE_KEY = "elementor-global-variables";
124
181
  var STORAGE_WATERMARK_KEY = "elementor-global-variables-watermark";
@@ -259,6 +316,9 @@ var service = {
259
316
  variables: () => {
260
317
  return storage.load();
261
318
  },
319
+ getWatermark: () => {
320
+ return storage.state.watermark;
321
+ },
262
322
  init: () => {
263
323
  service.load();
264
324
  },
@@ -361,6 +421,40 @@ var service = {
361
421
  variable: restoredVariable
362
422
  };
363
423
  });
424
+ },
425
+ batchSave: (originalVariables, currentVariables) => {
426
+ const operations = buildOperationsArray(originalVariables, currentVariables);
427
+ const batchPayload = { operations, watermark: storage.state.watermark };
428
+ return apiClient.batch(batchPayload).then((response) => {
429
+ const { success, data: payload } = response.data;
430
+ if (!success) {
431
+ throw new Error("Unexpected response from server");
432
+ }
433
+ return payload;
434
+ }).then((data) => {
435
+ const { results, watermark } = data;
436
+ handleWatermark(OP_RW, watermark);
437
+ if (results) {
438
+ results.forEach((result) => {
439
+ if (result.variable) {
440
+ const { id: variableId, ...variableData } = result.variable;
441
+ if (result.type === "create") {
442
+ storage.add(variableId, variableData);
443
+ } else {
444
+ storage.update(variableId, variableData);
445
+ }
446
+ styleVariablesRepository.update({
447
+ [variableId]: variableData
448
+ });
449
+ }
450
+ });
451
+ }
452
+ return {
453
+ success: true,
454
+ watermark,
455
+ operations: operations.length
456
+ };
457
+ });
364
458
  }
365
459
  };
366
460
  var handleWatermark = (operation, newWatermark) => {
@@ -512,7 +606,8 @@ var useFilteredVariables = (searchValue, propTypeKey) => {
512
606
  return {
513
607
  list: searchFilteredVariables,
514
608
  hasMatches: searchFilteredVariables.length > 0,
515
- isSourceNotEmpty: typeFilteredVariables.length > 0
609
+ isSourceNotEmpty: typeFilteredVariables.length > 0,
610
+ hasNoCompatibleVariables: baseVariables.length > 0 && typeFilteredVariables.length === 0
516
611
  };
517
612
  };
518
613
  var useVariableSelectionFilter = (variables) => {
@@ -998,7 +1093,21 @@ function VariablesManagerPanel() {
998
1093
  const [isDirty, setIsDirty] = (0, import_react7.useState)(false);
999
1094
  const [variables, setVariables] = (0, import_react7.useState)(getVariables(false));
1000
1095
  const [deletedVariables, setDeletedVariables] = (0, import_react7.useState)([]);
1096
+ const [isSaving, setIsSaving] = (0, import_react7.useState)(false);
1001
1097
  usePreventUnload(isDirty);
1098
+ const handleSave = (0, import_react7.useCallback)(async () => {
1099
+ setIsSaving(true);
1100
+ const originalVariables = getVariables(false);
1101
+ const result = await service.batchSave(originalVariables, variables);
1102
+ if (result.success) {
1103
+ await service.load();
1104
+ const updatedVariables = service.variables();
1105
+ setVariables(updatedVariables);
1106
+ setIsDirty(false);
1107
+ setDeletedVariables([]);
1108
+ }
1109
+ setIsSaving(false);
1110
+ }, [variables]);
1002
1111
  const menuActions = [
1003
1112
  {
1004
1113
  name: (0, import_i18n5.__)("Delete", "elementor"),
@@ -1040,7 +1149,18 @@ function VariablesManagerPanel() {
1040
1149
  onChange: handleOnChange
1041
1150
  }
1042
1151
  )
1043
- ), /* @__PURE__ */ React8.createElement(import_editor_panels.PanelFooter, null, /* @__PURE__ */ React8.createElement(import_ui8.Button, { fullWidth: true, size: "small", color: "global", variant: "contained", disabled: !isDirty }, (0, import_i18n5.__)("Save changes", "elementor"))))));
1152
+ ), /* @__PURE__ */ React8.createElement(import_editor_panels.PanelFooter, null, /* @__PURE__ */ React8.createElement(
1153
+ import_ui8.Button,
1154
+ {
1155
+ fullWidth: true,
1156
+ size: "small",
1157
+ color: "global",
1158
+ variant: "contained",
1159
+ disabled: !isDirty || isSaving,
1160
+ onClick: handleSave
1161
+ },
1162
+ (0, import_i18n5.__)("Save changes", "elementor")
1163
+ )))));
1044
1164
  }
1045
1165
  var CloseButton = ({ onClose, ...props }) => /* @__PURE__ */ React8.createElement(import_ui8.IconButton, { size: "small", color: "secondary", onClick: onClose, "aria-label": "Close", ...props }, /* @__PURE__ */ React8.createElement(import_icons3.XIcon, { fontSize: "small" }));
1046
1166
  var ErrorBoundaryFallback = () => /* @__PURE__ */ React8.createElement(import_ui8.Box, { role: "alert", sx: { minHeight: "100%", p: 2 } }, /* @__PURE__ */ React8.createElement(import_ui8.Alert, { severity: "error", sx: { mb: 2, maxWidth: 400, textAlign: "center" } }, /* @__PURE__ */ React8.createElement("strong", null, (0, import_i18n5.__)("Something went wrong", "elementor"))));
@@ -1530,17 +1650,47 @@ var import_icons9 = require("@elementor/icons");
1530
1650
  var import_ui19 = require("@elementor/ui");
1531
1651
  var import_i18n13 = require("@wordpress/i18n");
1532
1652
 
1533
- // src/components/ui/menu-item-content.tsx
1653
+ // src/components/ui/empty-state.tsx
1534
1654
  var React15 = __toESM(require("react"));
1535
- var import_editor_ui5 = require("@elementor/editor-ui");
1536
- var import_icons8 = require("@elementor/icons");
1537
1655
  var import_ui15 = require("@elementor/ui");
1538
1656
  var import_i18n10 = require("@wordpress/i18n");
1657
+ var EmptyState = ({ icon, title, message, onAdd }) => {
1658
+ const canAdd = usePermissions().canAdd();
1659
+ return /* @__PURE__ */ React15.createElement(
1660
+ import_ui15.Stack,
1661
+ {
1662
+ gap: 1,
1663
+ alignItems: "center",
1664
+ justifyContent: "center",
1665
+ height: "100%",
1666
+ color: "text.secondary",
1667
+ sx: { p: 2.5, pb: 5.5 }
1668
+ },
1669
+ icon,
1670
+ canAdd ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Content, { title, message }), onAdd && /* @__PURE__ */ React15.createElement(import_ui15.Button, { variant: "outlined", color: "secondary", size: "small", onClick: onAdd }, (0, import_i18n10.__)("Create a variable", "elementor"))) : /* @__PURE__ */ React15.createElement(
1671
+ Content,
1672
+ {
1673
+ title: (0, import_i18n10.__)("There are no variables", "elementor"),
1674
+ message: (0, import_i18n10.__)("With your current role, you can only connect and detach variables.", "elementor")
1675
+ }
1676
+ )
1677
+ );
1678
+ };
1679
+ function Content({ title, message }) {
1680
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(import_ui15.Typography, { align: "center", variant: "subtitle2" }, title), /* @__PURE__ */ React15.createElement(import_ui15.Typography, { align: "center", variant: "caption", maxWidth: "180px" }, message));
1681
+ }
1682
+
1683
+ // src/components/ui/menu-item-content.tsx
1684
+ var React16 = __toESM(require("react"));
1685
+ var import_editor_ui5 = require("@elementor/editor-ui");
1686
+ var import_icons8 = require("@elementor/icons");
1687
+ var import_ui16 = require("@elementor/ui");
1688
+ var import_i18n11 = require("@wordpress/i18n");
1539
1689
  var SIZE3 = "tiny";
1540
1690
  var MenuItemContent = ({ item }) => {
1541
1691
  const onEdit = item.onEdit;
1542
- return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(import_ui15.ListItemIcon, null, item.icon), /* @__PURE__ */ React15.createElement(
1543
- import_ui15.Box,
1692
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(import_ui16.ListItemIcon, null, item.icon), /* @__PURE__ */ React16.createElement(
1693
+ import_ui16.Box,
1544
1694
  {
1545
1695
  sx: {
1546
1696
  flex: 1,
@@ -1550,70 +1700,47 @@ var MenuItemContent = ({ item }) => {
1550
1700
  gap: 1
1551
1701
  }
1552
1702
  },
1553
- /* @__PURE__ */ React15.createElement(
1703
+ /* @__PURE__ */ React16.createElement(
1554
1704
  import_editor_ui5.EllipsisWithTooltip,
1555
1705
  {
1556
1706
  title: item.label || item.value,
1557
- as: import_ui15.Typography,
1707
+ as: import_ui16.Typography,
1558
1708
  variant: "caption",
1559
1709
  color: "text.primary",
1560
1710
  sx: { marginTop: "1px", lineHeight: "2" },
1561
1711
  maxWidth: "50%"
1562
1712
  }
1563
1713
  ),
1564
- item.secondaryText && /* @__PURE__ */ React15.createElement(
1714
+ item.secondaryText && /* @__PURE__ */ React16.createElement(
1565
1715
  import_editor_ui5.EllipsisWithTooltip,
1566
1716
  {
1567
1717
  title: item.secondaryText,
1568
- as: import_ui15.Typography,
1718
+ as: import_ui16.Typography,
1569
1719
  variant: "caption",
1570
1720
  color: "text.tertiary",
1571
1721
  sx: { marginTop: "1px", lineHeight: "1" },
1572
1722
  maxWidth: "50%"
1573
1723
  }
1574
1724
  )
1575
- ), !!onEdit && /* @__PURE__ */ React15.createElement(
1576
- import_ui15.IconButton,
1725
+ ), !!onEdit && /* @__PURE__ */ React16.createElement(
1726
+ import_ui16.IconButton,
1577
1727
  {
1578
1728
  sx: { mx: 1, opacity: "0" },
1579
1729
  onClick: (e) => {
1580
1730
  e.stopPropagation();
1581
1731
  onEdit(item.value);
1582
1732
  },
1583
- "aria-label": (0, import_i18n10.__)("Edit", "elementor")
1733
+ "aria-label": (0, import_i18n11.__)("Edit", "elementor")
1584
1734
  },
1585
- /* @__PURE__ */ React15.createElement(import_icons8.EditIcon, { color: "action", fontSize: SIZE3 })
1735
+ /* @__PURE__ */ React16.createElement(import_icons8.EditIcon, { color: "action", fontSize: SIZE3 })
1586
1736
  ));
1587
1737
  };
1588
1738
 
1589
1739
  // src/components/ui/no-search-results.tsx
1590
- var React16 = __toESM(require("react"));
1591
- var import_ui16 = require("@elementor/ui");
1592
- var import_i18n11 = require("@wordpress/i18n");
1593
- var NoSearchResults = ({ searchValue, onClear, icon }) => {
1594
- return /* @__PURE__ */ React16.createElement(
1595
- import_ui16.Stack,
1596
- {
1597
- gap: 1,
1598
- alignItems: "center",
1599
- justifyContent: "center",
1600
- height: "100%",
1601
- p: 2.5,
1602
- color: "text.secondary",
1603
- sx: { pb: 3.5 }
1604
- },
1605
- icon,
1606
- /* @__PURE__ */ React16.createElement(import_ui16.Typography, { align: "center", variant: "subtitle2" }, (0, import_i18n11.__)("Sorry, nothing matched", "elementor"), /* @__PURE__ */ React16.createElement("br", null), "\u201C", searchValue, "\u201D."),
1607
- /* @__PURE__ */ React16.createElement(import_ui16.Typography, { align: "center", variant: "caption", sx: { display: "flex", flexDirection: "column" } }, (0, import_i18n11.__)("Try something else.", "elementor"), /* @__PURE__ */ React16.createElement(import_ui16.Link, { color: "text.secondary", variant: "caption", component: "button", onClick: onClear }, (0, import_i18n11.__)("Clear & try again", "elementor")))
1608
- );
1609
- };
1610
-
1611
- // src/components/ui/no-variables.tsx
1612
1740
  var React17 = __toESM(require("react"));
1613
1741
  var import_ui17 = require("@elementor/ui");
1614
1742
  var import_i18n12 = require("@wordpress/i18n");
1615
- var NoVariables = ({ icon, title, onAdd }) => {
1616
- const canAdd = usePermissions().canAdd();
1743
+ var NoSearchResults = ({ searchValue, onClear, icon }) => {
1617
1744
  return /* @__PURE__ */ React17.createElement(
1618
1745
  import_ui17.Stack,
1619
1746
  {
@@ -1621,31 +1748,15 @@ var NoVariables = ({ icon, title, onAdd }) => {
1621
1748
  alignItems: "center",
1622
1749
  justifyContent: "center",
1623
1750
  height: "100%",
1751
+ p: 2.5,
1624
1752
  color: "text.secondary",
1625
- sx: { p: 2.5, pb: 5.5 }
1753
+ sx: { pb: 3.5 }
1626
1754
  },
1627
1755
  icon,
1628
- canAdd ? /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement(
1629
- NoVariablesContent,
1630
- {
1631
- title: title || (0, import_i18n12.__)("Create your first variable", "elementor"),
1632
- message: (0, import_i18n12.__)(
1633
- "Variables are saved attributes that you can apply anywhere on your site.",
1634
- "elementor"
1635
- )
1636
- }
1637
- ), onAdd && /* @__PURE__ */ React17.createElement(import_ui17.Button, { variant: "outlined", color: "secondary", size: "small", onClick: onAdd }, (0, import_i18n12.__)("Create a variable", "elementor"))) : /* @__PURE__ */ React17.createElement(
1638
- NoVariablesContent,
1639
- {
1640
- title: (0, import_i18n12.__)("There are no variables", "elementor"),
1641
- message: (0, import_i18n12.__)("With your current role, you can only connect and detach variables.", "elementor")
1642
- }
1643
- )
1756
+ /* @__PURE__ */ React17.createElement(import_ui17.Typography, { align: "center", variant: "subtitle2" }, (0, import_i18n12.__)("Sorry, nothing matched", "elementor"), /* @__PURE__ */ React17.createElement("br", null), "\u201C", searchValue, "\u201D."),
1757
+ /* @__PURE__ */ React17.createElement(import_ui17.Typography, { align: "center", variant: "caption", sx: { display: "flex", flexDirection: "column" } }, (0, import_i18n12.__)("Try something else.", "elementor"), /* @__PURE__ */ React17.createElement(import_ui17.Link, { color: "text.secondary", variant: "caption", component: "button", onClick: onClear }, (0, import_i18n12.__)("Clear & try again", "elementor")))
1644
1758
  );
1645
1759
  };
1646
- function NoVariablesContent({ title, message }) {
1647
- return /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement(import_ui17.Typography, { align: "center", variant: "subtitle2" }, title), /* @__PURE__ */ React17.createElement(import_ui17.Typography, { align: "center", variant: "caption", maxWidth: "180px" }, message));
1648
- }
1649
1760
 
1650
1761
  // src/components/ui/styled-menu-list.tsx
1651
1762
  var import_ui18 = require("@elementor/ui");
@@ -1688,7 +1799,8 @@ var VariablesSelection = ({ closePopover, onAdd, onEdit, onSettings }) => {
1688
1799
  const {
1689
1800
  list: variables,
1690
1801
  hasMatches: hasSearchResults,
1691
- isSourceNotEmpty: hasVariables
1802
+ isSourceNotEmpty: hasVariables,
1803
+ hasNoCompatibleVariables
1692
1804
  } = useFilteredVariables(searchValue, propTypeUtil.key);
1693
1805
  const handleSetVariable = (key) => {
1694
1806
  setVariable(key);
@@ -1772,7 +1884,29 @@ var VariablesSelection = ({ closePopover, onAdd, onEdit, onSettings }) => {
1772
1884
  onClear: handleClearSearch,
1773
1885
  icon: /* @__PURE__ */ React18.createElement(VariableIcon, { fontSize: "large" })
1774
1886
  }
1775
- ), !hasVariables && /* @__PURE__ */ React18.createElement(NoVariables, { title: noVariableTitle, icon: /* @__PURE__ */ React18.createElement(VariableIcon, { fontSize: "large" }), onAdd }));
1887
+ ), !hasVariables && !hasNoCompatibleVariables && /* @__PURE__ */ React18.createElement(
1888
+ EmptyState,
1889
+ {
1890
+ title: noVariableTitle,
1891
+ message: (0, import_i18n13.__)(
1892
+ "Variables are saved attributes that you can apply anywhere on your site.",
1893
+ "elementor"
1894
+ ),
1895
+ icon: /* @__PURE__ */ React18.createElement(VariableIcon, { fontSize: "large" }),
1896
+ onAdd
1897
+ }
1898
+ ), hasNoCompatibleVariables && /* @__PURE__ */ React18.createElement(
1899
+ EmptyState,
1900
+ {
1901
+ title: (0, import_i18n13.__)("No compatible variables", "elementor"),
1902
+ message: (0, import_i18n13.__)(
1903
+ "Looks like none of your variables work with this control. Create a new variable to use it here.",
1904
+ "elementor"
1905
+ ),
1906
+ icon: /* @__PURE__ */ React18.createElement(VariableIcon, { fontSize: "large" }),
1907
+ onAdd
1908
+ }
1909
+ ));
1776
1910
  };
1777
1911
 
1778
1912
  // src/components/variable-selection-popover.tsx