@atlaskit/top-layer 0.8.0 → 0.10.0

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 (130) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +5 -1
  3. package/dialog/create-close-event/package.json +17 -0
  4. package/dist/cjs/animations/presets.js +11 -4
  5. package/dist/cjs/arrow/index.js +131 -0
  6. package/dist/cjs/arrow/types.js +5 -0
  7. package/dist/cjs/dialog/dialog-content.js +23 -6
  8. package/dist/cjs/entry-points/dialog/create-close-event.js +12 -0
  9. package/dist/cjs/entry-points/placement-map.js +6 -5
  10. package/dist/cjs/entry-points/popover/create-close-event.js +12 -0
  11. package/dist/cjs/focus/focus.js +73 -19
  12. package/dist/cjs/index.js +3 -4
  13. package/dist/cjs/internal/resolve-css-length-to-pixels.js +61 -10
  14. package/dist/cjs/internal/role-types.js +40 -9
  15. package/dist/cjs/internal/set-style.js +23 -25
  16. package/dist/cjs/internal/use-anchor-position.js +109 -68
  17. package/dist/cjs/internal/use-focus-wrap.js +13 -2
  18. package/dist/cjs/internal/use-initial-focus.js +31 -10
  19. package/dist/cjs/placement-map/index.js +9 -110
  20. package/dist/cjs/placement-map/legacy-placements.js +113 -0
  21. package/dist/cjs/popover/popover.js +44 -11
  22. package/dist/cjs/popup/popup-content.js +19 -13
  23. package/dist/cjs/popup/popup-context.js +2 -0
  24. package/dist/cjs/popup/popup-surface.js +6 -0
  25. package/dist/cjs/popup/popup-trigger-function.js +1 -1
  26. package/dist/cjs/popup/popup-trigger.js +17 -37
  27. package/dist/cjs/popup/popup.js +21 -10
  28. package/dist/cjs/use-arrow-navigation/use-arrow-navigation.js +27 -10
  29. package/dist/es2019/animations/presets.js +10 -3
  30. package/dist/es2019/arrow/index.js +269 -0
  31. package/dist/es2019/arrow/types.js +1 -0
  32. package/dist/es2019/dialog/dialog-content.js +23 -6
  33. package/dist/es2019/entry-points/create-close-event.js +7 -0
  34. package/dist/es2019/entry-points/dialog/create-close-event.js +1 -0
  35. package/dist/es2019/entry-points/placement-map.js +2 -1
  36. package/dist/es2019/entry-points/popover/create-close-event.js +1 -0
  37. package/dist/es2019/focus/focus.js +58 -7
  38. package/dist/es2019/index.js +3 -4
  39. package/dist/es2019/internal/resolve-css-length-to-pixels.js +60 -7
  40. package/dist/es2019/internal/role-types.js +40 -9
  41. package/dist/es2019/internal/set-style.js +19 -22
  42. package/dist/es2019/internal/use-anchor-position.js +97 -54
  43. package/dist/es2019/internal/use-focus-wrap.js +14 -3
  44. package/dist/es2019/internal/use-initial-focus.js +32 -11
  45. package/dist/es2019/placement-map/index.js +8 -108
  46. package/dist/es2019/placement-map/legacy-placements.js +107 -0
  47. package/dist/es2019/popover/popover.js +44 -10
  48. package/dist/es2019/popup/popup-content.js +20 -14
  49. package/dist/es2019/popup/popup-context.js +2 -0
  50. package/dist/es2019/popup/popup-surface.js +6 -0
  51. package/dist/es2019/popup/popup-trigger-function.js +1 -1
  52. package/dist/es2019/popup/popup-trigger.js +17 -37
  53. package/dist/es2019/popup/popup.js +20 -9
  54. package/dist/es2019/use-arrow-navigation/use-arrow-navigation.js +27 -10
  55. package/dist/esm/animations/presets.js +10 -3
  56. package/dist/esm/arrow/index.js +124 -0
  57. package/dist/esm/arrow/types.js +1 -0
  58. package/dist/esm/dialog/dialog-content.js +23 -6
  59. package/dist/esm/entry-points/create-close-event.js +7 -0
  60. package/dist/esm/entry-points/dialog/create-close-event.js +1 -0
  61. package/dist/esm/entry-points/placement-map.js +2 -1
  62. package/dist/esm/entry-points/popover/create-close-event.js +1 -0
  63. package/dist/esm/focus/focus.js +72 -19
  64. package/dist/esm/index.js +3 -4
  65. package/dist/esm/internal/resolve-css-length-to-pixels.js +61 -10
  66. package/dist/esm/internal/role-types.js +39 -9
  67. package/dist/esm/internal/set-style.js +23 -25
  68. package/dist/esm/internal/use-anchor-position.js +110 -64
  69. package/dist/esm/internal/use-focus-wrap.js +14 -3
  70. package/dist/esm/internal/use-initial-focus.js +32 -11
  71. package/dist/esm/placement-map/index.js +8 -108
  72. package/dist/esm/placement-map/legacy-placements.js +107 -0
  73. package/dist/esm/popover/popover.js +44 -11
  74. package/dist/esm/popup/popup-content.js +20 -14
  75. package/dist/esm/popup/popup-context.js +2 -0
  76. package/dist/esm/popup/popup-surface.js +6 -0
  77. package/dist/esm/popup/popup-trigger-function.js +1 -1
  78. package/dist/esm/popup/popup-trigger.js +17 -37
  79. package/dist/esm/popup/popup.js +21 -10
  80. package/dist/esm/use-arrow-navigation/use-arrow-navigation.js +27 -10
  81. package/dist/types/arrow/index.d.ts +24 -0
  82. package/dist/types/arrow/types.d.ts +29 -0
  83. package/dist/types/dialog/dialog-content.d.ts +5 -1
  84. package/dist/types/dialog/types.d.ts +50 -24
  85. package/dist/types/entry-points/create-close-event.d.ts +7 -0
  86. package/dist/types/entry-points/dialog/create-close-event.d.ts +2 -0
  87. package/dist/types/entry-points/placement-map.d.ts +2 -1
  88. package/dist/types/entry-points/popover/create-close-event.d.ts +2 -0
  89. package/dist/types/focus/focus.d.ts +8 -0
  90. package/dist/types/internal/resolve-css-length-to-pixels.d.ts +4 -6
  91. package/dist/types/internal/role-types.d.ts +25 -4
  92. package/dist/types/internal/set-style.d.ts +2 -19
  93. package/dist/types/internal/use-anchor-position.d.ts +2 -3
  94. package/dist/types/internal/use-initial-focus.d.ts +5 -4
  95. package/dist/types/placement-map/index.d.ts +8 -21
  96. package/dist/types/placement-map/legacy-placements.d.ts +21 -0
  97. package/dist/types/popover/popover.d.ts +1 -1
  98. package/dist/types/popover/types.d.ts +20 -19
  99. package/dist/types/popup/popup-surface.d.ts +6 -0
  100. package/dist/types/popup/popup-trigger-function.d.ts +10 -6
  101. package/dist/types/popup/popup-trigger.d.ts +8 -17
  102. package/dist/types/popup/popup.d.ts +2 -2
  103. package/dist/types/popup/types.d.ts +68 -22
  104. package/dist/types/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  105. package/dist/types-ts4.5/arrow/index.d.ts +24 -0
  106. package/dist/types-ts4.5/arrow/types.d.ts +29 -0
  107. package/dist/types-ts4.5/dialog/dialog-content.d.ts +5 -1
  108. package/dist/types-ts4.5/dialog/types.d.ts +50 -24
  109. package/dist/types-ts4.5/entry-points/create-close-event.d.ts +7 -0
  110. package/dist/types-ts4.5/entry-points/dialog/create-close-event.d.ts +2 -0
  111. package/dist/types-ts4.5/entry-points/placement-map.d.ts +2 -1
  112. package/dist/types-ts4.5/entry-points/popover/create-close-event.d.ts +2 -0
  113. package/dist/types-ts4.5/focus/focus.d.ts +8 -0
  114. package/dist/types-ts4.5/internal/resolve-css-length-to-pixels.d.ts +4 -6
  115. package/dist/types-ts4.5/internal/role-types.d.ts +25 -4
  116. package/dist/types-ts4.5/internal/set-style.d.ts +2 -19
  117. package/dist/types-ts4.5/internal/use-anchor-position.d.ts +2 -3
  118. package/dist/types-ts4.5/internal/use-initial-focus.d.ts +5 -4
  119. package/dist/types-ts4.5/placement-map/index.d.ts +8 -21
  120. package/dist/types-ts4.5/placement-map/legacy-placements.d.ts +39 -0
  121. package/dist/types-ts4.5/popover/popover.d.ts +1 -1
  122. package/dist/types-ts4.5/popover/types.d.ts +20 -19
  123. package/dist/types-ts4.5/popup/popup-surface.d.ts +6 -0
  124. package/dist/types-ts4.5/popup/popup-trigger-function.d.ts +10 -6
  125. package/dist/types-ts4.5/popup/popup-trigger.d.ts +8 -17
  126. package/dist/types-ts4.5/popup/popup.d.ts +2 -2
  127. package/dist/types-ts4.5/popup/types.d.ts +68 -22
  128. package/dist/types-ts4.5/use-arrow-navigation/use-arrow-navigation.d.ts +19 -5
  129. package/package.json +3 -1
  130. package/popover/create-close-event/package.json +17 -0
@@ -10,6 +10,7 @@
10
10
  * restrict which focusable elements are included.
11
11
  */
12
12
 
13
+ import { getDocument } from '@atlaskit/browser-apis';
13
14
  var selectors = function () {
14
15
  // Common exclusion filter applied to every selector.
15
16
  var not = ':not([tabindex="-1"]):not([aria-disabled="true"]):not([aria-hidden="true"])';
@@ -17,9 +18,54 @@ var selectors = function () {
17
18
  var focusable = ["a[href]".concat(not), "area[href]".concat(not), "input".concat(notDisabled, ":not([type=\"hidden\"])"), "select".concat(notDisabled), "textarea".concat(notDisabled), "button".concat(notDisabled), "iframe".concat(not), "audio[controls]".concat(not), "video[controls]".concat(not), "[contenteditable]:not([contenteditable=\"false\"])".concat(not), "[tabindex]".concat(notDisabled).concat(not)];
18
19
  return {
19
20
  focusable: focusable.join(','),
20
- focused: focusable.join(':focus,') + ':focus'
21
+ // Build the `:focus` selector
22
+ focused: focusable.map(function (selector) {
23
+ return "".concat(selector, ":focus");
24
+ }).join(',')
21
25
  };
22
26
  }();
27
+ var topLayerSelector = '[popover], dialog, [role="dialog"], [role="alertdialog"]';
28
+
29
+ /**
30
+ * Returns true when `element` and `container` resolve to the same nearest
31
+ * top-layer host (or both resolve to `null`, meaning the document layer).
32
+ */
33
+ function isInSameLayer(_ref) {
34
+ var element = _ref.element,
35
+ container = _ref.container;
36
+ var elementLayer = element.closest(topLayerSelector);
37
+ var containerLayer = container.closest(topLayerSelector);
38
+ // Both elements are in the "document" layer
39
+ if (!elementLayer && !containerLayer) {
40
+ return true;
41
+ }
42
+ return elementLayer === containerLayer;
43
+ }
44
+
45
+ /**
46
+ * Returns `true` when `document.activeElement` is inside a nested top-layer
47
+ * descendant of `container` (a `[popover]`, `<dialog>`, `[role="dialog"]` or
48
+ * `[role="alertdialog"]` that is itself a descendant of `container`).
49
+ */
50
+ export function isNestedLayerFocused(_ref2) {
51
+ var _getDocument;
52
+ var container = _ref2.container;
53
+ var focused = (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement;
54
+ if (!(focused instanceof HTMLElement)) {
55
+ return false;
56
+ }
57
+
58
+ // Cannot be in a nested layer if it is not in the container
59
+ if (!container.contains(focused)) {
60
+ return false;
61
+ }
62
+
63
+ // If not in the same layer, must be in a nested layer
64
+ return !isInSameLayer({
65
+ element: focused,
66
+ container: container
67
+ });
68
+ }
23
69
 
24
70
  /**
25
71
  * An optional callback to restrict which focusable elements are included.
@@ -29,17 +75,21 @@ var selectors = function () {
29
75
  /**
30
76
  * Returns all focusable HTMLElements within the container, optionally filtered.
31
77
  *
32
- * Note: this searches the full subtree and does not stop at nested
33
- * popover/dialog boundaries. In practice this is fine because nested
34
- * popovers are not rendered until they are opened, so their content
35
- * is not in the DOM when the parent's initial focus runs. If that
36
- * assumption changes, this would need to be scoped to the current layer.
78
+ * Focusables that belong to a nested top-layer scope (a `[popover]` or
79
+ * `<dialog>` descendant of the container) are excluded - those elements
80
+ * are owned by the inner layer's focus management. The container itself
81
+ * is allowed to be a popover/dialog; only nested ones are filtered.
37
82
  */
38
- function getFocusables(_ref) {
39
- var container = _ref.container,
40
- filter = _ref.filter;
83
+ function getFocusables(_ref3) {
84
+ var container = _ref3.container,
85
+ filter = _ref3.filter;
41
86
  return Array.from(container.querySelectorAll(selectors.focusable)).filter(function (element) {
42
87
  return element instanceof HTMLElement;
88
+ }).filter(function (element) {
89
+ return isInSameLayer({
90
+ element: element,
91
+ container: container
92
+ });
43
93
  }).filter(function (element) {
44
94
  return !filter || filter(element, container);
45
95
  });
@@ -48,9 +98,10 @@ function getFocusables(_ref) {
48
98
  /**
49
99
  * Returns the first focusable element within the container.
50
100
  */
51
- export function getFirstFocusable(_ref2) {
52
- var container = _ref2.container,
53
- filter = _ref2.filter;
101
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
102
+ export function getFirstFocusable(_ref4) {
103
+ var container = _ref4.container,
104
+ filter = _ref4.filter;
54
105
  var items = getFocusables({
55
106
  container: container,
56
107
  filter: filter
@@ -61,9 +112,10 @@ export function getFirstFocusable(_ref2) {
61
112
  /**
62
113
  * Returns the last focusable element within the container.
63
114
  */
64
- export function getLastFocusable(_ref3) {
65
- var container = _ref3.container,
66
- filter = _ref3.filter;
115
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
116
+ export function getLastFocusable(_ref5) {
117
+ var container = _ref5.container,
118
+ filter = _ref5.filter;
67
119
  var items = getFocusables({
68
120
  container: container,
69
121
  filter: filter
@@ -78,11 +130,12 @@ export function getLastFocusable(_ref3) {
78
130
  * Returns `null` if there is no focused element within the container or if
79
131
  * the focused element is not in the list of focusable elements.
80
132
  */
81
- export function getNextFocusable(_ref4) {
133
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
134
+ export function getNextFocusable(_ref6) {
82
135
  var _all$nextIndex;
83
- var container = _ref4.container,
84
- direction = _ref4.direction,
85
- filter = _ref4.filter;
136
+ var container = _ref6.container,
137
+ direction = _ref6.direction,
138
+ filter = _ref6.filter;
86
139
  var all = getFocusables({
87
140
  container: container,
88
141
  filter: filter
package/dist/esm/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // This file exists for module resolution purposes.
2
2
  // Not exporting anything from the root entry point.
3
- // Please use individual entry points for minimum bundle size:
4
3
  //
5
- // import { Popup } from '@atlaskit/top-layer/popup';
6
- // import { Dialog } from '@atlaskit/top-layer/dialog';
7
- // import { Popover } from '@atlaskit/top-layer/popover';
4
+ // Use individual entry points for minimum bundle size - the full list of
5
+ // available subpaths is the `"exports"` field in this package's
6
+ // `package.json`. The README documents the most common ones.
@@ -1,3 +1,32 @@
1
+ var elementCaches = new WeakMap();
2
+ function canCacheResolvedLength(value) {
3
+ // Custom-property values can be retargeted by inline overrides on the
4
+ // consumer's wrapper, so we cannot prove a cached pixel value is still valid.
5
+ return !value.includes('var(');
6
+ }
7
+ function getCachedResolvedLength(_ref) {
8
+ var _cacheForElement$get;
9
+ var element = _ref.element,
10
+ value = _ref.value;
11
+ if (!canCacheResolvedLength(value)) {
12
+ return null;
13
+ }
14
+ var cacheForElement = elementCaches.get(element);
15
+ return (_cacheForElement$get = cacheForElement === null || cacheForElement === void 0 ? void 0 : cacheForElement.get(value)) !== null && _cacheForElement$get !== void 0 ? _cacheForElement$get : null;
16
+ }
17
+ function cacheResolvedLength(_ref2) {
18
+ var _elementCaches$get;
19
+ var element = _ref2.element,
20
+ value = _ref2.value,
21
+ result = _ref2.result;
22
+ if (!canCacheResolvedLength(value)) {
23
+ return;
24
+ }
25
+ var cacheForElement = (_elementCaches$get = elementCaches.get(element)) !== null && _elementCaches$get !== void 0 ? _elementCaches$get : new Map();
26
+ cacheForElement.set(value, result);
27
+ elementCaches.set(element, cacheForElement);
28
+ }
29
+
1
30
  /**
2
31
  * Resolves a CSS length value to a pixel number using a hidden DOM probe.
3
32
  *
@@ -8,27 +37,39 @@
8
37
  * the browser do the math.
9
38
  *
10
39
  * The probe is appended to `container` so it inherits the same containing
11
- * block, font size, and custom-property scope as the popover. We use `width`
12
- * because it accepts any CSS length, including negative `calc()` results
13
- * (negative widths are clamped to 0 — see note below).
40
+ * block, font size, and custom-property scope as the popover.
14
41
  *
15
- * Cost: one synchronous reflow per call. The fallback only resolves on
16
- * placement/offset changes, not on every scroll/resize update, so this is
17
- * not a hot path.
42
+ * Cost: one synchronous reflow on first cacheable resolution per
43
+ * (container, value) pair, then a `WeakMap` lookup. Resolutions involving
44
+ * `var(` are intentionally not cached.
18
45
  */
19
- export function resolveCssLengthToPixels(_ref) {
20
- var value = _ref.value,
21
- container = _ref.container;
46
+ export function resolveCssLengthToPixels(_ref3) {
47
+ var value = _ref3.value,
48
+ container = _ref3.container;
22
49
  if (typeof value === 'number') {
23
50
  return value;
24
51
  }
25
52
 
53
+ // Fast path: unitless zero is universally zero pixels - accept `'0'` and
54
+ // `'-0'` without a probe. (Consumers occasionally pass `'0'` instead of
55
+ // the number `0`.)
56
+ if (value === '0' || value === '-0') {
57
+ return 0;
58
+ }
59
+
26
60
  // Fast path: bare `${n}px` strings come from the consumer normalisation
27
61
  // in `getPlacement`. Avoid a reflow when we can.
28
62
  var pxMatch = value.match(/^(-?\d+(?:\.\d+)?)px$/);
29
63
  if (pxMatch) {
30
64
  return Number(pxMatch[1]);
31
65
  }
66
+ var cached = getCachedResolvedLength({
67
+ element: container,
68
+ value: value
69
+ });
70
+ if (cached !== null) {
71
+ return cached;
72
+ }
32
73
  var probe = container.ownerDocument.createElement('div');
33
74
  probe.style.position = 'absolute';
34
75
  probe.style.visibility = 'hidden';
@@ -40,8 +81,18 @@ export function resolveCssLengthToPixels(_ref) {
40
81
  // preserved. `getComputedStyle` returns the resolved pixel value for
41
82
  // margin-* properties even when the source was a token / calc / var.
42
83
  probe.style.marginLeft = value;
84
+ // Hide the probe from assistive tech and event targeting; defensive
85
+ // against `getElementsByTagName('div')` walks in consumer code.
86
+ probe.setAttribute('aria-hidden', 'true');
87
+ probe.setAttribute('inert', '');
43
88
  container.appendChild(probe);
44
89
  var resolved = parseFloat(getComputedStyle(probe).marginLeft);
45
90
  container.removeChild(probe);
46
- return Number.isFinite(resolved) ? resolved : 0;
91
+ var result = Number.isFinite(resolved) ? resolved : 0;
92
+ cacheResolvedLength({
93
+ element: container,
94
+ value: value,
95
+ result: result
96
+ });
97
+ return result;
47
98
  }
@@ -1,6 +1,15 @@
1
1
  /**
2
2
  * Valid values for the `aria-haspopup` attribute.
3
- * Derived from the HTML spec — maps popover roles to what the trigger announces.
3
+ *
4
+ * Derived from the HTML spec - maps popover roles to what the trigger
5
+ * announces. `undefined` means the attribute is omitted entirely (used for
6
+ * non-popup roles like `tooltip`/`status`/`alert`/`note`/`log` where
7
+ * `aria-haspopup` would be misleading).
8
+ *
9
+ * `true` is intentionally NOT in the union - the runtime only ever produces
10
+ * the explicit string forms, so widening the type would invite consumers to
11
+ * pass `true` and get an `aria-haspopup="true"` serialisation that the
12
+ * runtime no longer emits.
4
13
  */
5
14
 
6
15
  /**
@@ -22,21 +31,27 @@
22
31
  /**
23
32
  * ARIA role props where a role is always required.
24
33
  *
25
- * Used by `TPopupContentProps` — popup content must always declare
34
+ * Used by `TPopupContentProps`. Popup content must always declare
26
35
  * its role so assistive technology can identify the layer.
27
36
  */
28
37
 
29
38
  /**
30
39
  * ARIA role props where a role is optional.
31
40
  *
32
- * Used by `TPopoverProps` — the low-level primitive allows omitting
41
+ * Used by `TPopoverProps`. The low-level primitive allows omitting
33
42
  * the role for cases where it is set externally or not needed.
34
43
  */
35
44
 
36
45
  /**
37
46
  * Maps a popover content role to the correct `aria-haspopup` value
38
47
  * for its trigger element.
48
+ *
49
+ * Returns `undefined` for roles that are not popups in the ARIA sense
50
+ * (`tooltip`, `note`, `status`, `alert`, `log`). Triggers for those roles
51
+ * should not announce a popup at all - emitting `aria-haspopup="dialog"`
52
+ * on a tooltip trigger would mislead assistive technology.
39
53
  */
54
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
40
55
  export function roleToAriaHasPopup(_ref) {
41
56
  var role = _ref.role;
42
57
  if (role === 'menu') {
@@ -51,10 +66,25 @@ export function roleToAriaHasPopup(_ref) {
51
66
  if (role === 'grid') {
52
67
  return 'grid';
53
68
  }
54
- // Default: return 'dialog' for all unmapped roles (tooltip, alertdialog,
55
- // note, status, alert, log, etc). This is a kinder API — rather than
56
- // throwing on unknown roles, we fall back to the most common popup type.
57
- // New roles that need specific aria-haspopup values should be added
58
- // as explicit cases above.
59
- return 'dialog';
69
+ if (role === 'dialog' || role === 'alertdialog') {
70
+ return 'dialog';
71
+ }
72
+ // Non-popup roles (tooltip, note, status, alert, log) and the
73
+ // no-role case: omit `aria-haspopup` entirely. React drops the
74
+ // attribute when the value is `undefined`.
75
+ return undefined;
76
+ }
77
+ var rolesThatMoveFocus = new Set(['dialog', 'alertdialog', 'menu', 'listbox', 'tree', 'grid']);
78
+
79
+ /**
80
+ * Returns `true` when the given popover role moves focus into the popover
81
+ * on open, and `false` otherwise (including when `role` is `undefined`).
82
+ */
83
+ // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
84
+ export function shouldFocusIntoPopover(_ref2) {
85
+ var role = _ref2.role;
86
+ if (!role) {
87
+ return false;
88
+ }
89
+ return rolesThatMoveFocus.has(role);
60
90
  }
@@ -1,36 +1,34 @@
1
1
  /**
2
- * Sets inline styles on an element and returns a cleanup function
3
- * that removes only the styles that were applied.
4
- *
5
- * Uses `style.setProperty()` / `style.removeProperty()` so that
6
- * hyphenated CSS property names (e.g. `position-area`, `anchor-name`)
7
- * work correctly. These newer properties do not have camelCase
8
- * mappings in `CSSStyleDeclaration` yet.
9
- *
10
- * @example
11
- * ```ts
12
- * const cleanup = setStyle({ el: popover, styles: [
13
- * { property: 'position-anchor', value: '--my-anchor' },
14
- * { property: 'position-area', value: 'block-end' },
15
- * { property: 'margin', value: '0' },
16
- * ] });
17
- *
18
- * // Later: removes all three properties
19
- * cleanup();
20
- * ```
2
+ * Sets inline styles on an element and returns a cleanup function that
3
+ * restores the prior inline values (so we do not stomp consumer styles).
21
4
  */
22
5
  export function setStyle(_ref) {
23
6
  var el = _ref.el,
24
7
  styles = _ref.styles;
25
- styles.forEach(function (_ref2) {
26
- var property = _ref2.property,
27
- value = _ref2.value;
8
+ // Snapshot the prior inline value (NOT the computed style - we only want
9
+ // to restore values that the consumer/our previous run inlined). An empty
10
+ // string means "no inline value", in which case cleanup uses removeProperty.
11
+ var priorValues = styles.map(function (_ref2) {
12
+ var property = _ref2.property;
13
+ return {
14
+ property: property,
15
+ value: el.style.getPropertyValue(property)
16
+ };
17
+ });
18
+ styles.forEach(function (_ref3) {
19
+ var property = _ref3.property,
20
+ value = _ref3.value;
28
21
  el.style.setProperty(property, value);
29
22
  });
30
23
  return function cleanup() {
31
- styles.forEach(function (_ref3) {
32
- var property = _ref3.property;
33
- el.style.removeProperty(property);
24
+ priorValues.forEach(function (_ref4) {
25
+ var property = _ref4.property,
26
+ value = _ref4.value;
27
+ if (value === '') {
28
+ el.style.removeProperty(property);
29
+ return;
30
+ }
31
+ el.style.setProperty(property, value);
34
32
  });
35
33
  };
36
34
  }