material 3.4.0 → 3.4.1

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 (132) hide show
  1. package/README.md +1 -1
  2. package/dist/components/dialog/api.js +109 -109
  3. package/dist/components/dialog/config.js +0 -52
  4. package/dist/components/dialog/dialog.js +2 -2
  5. package/dist/components/dialog/features.js +113 -65
  6. package/dist/components/dialog/types.d.ts +2 -3
  7. package/dist/core/dom/focus.js +2 -2
  8. package/dist/elements/css/dialog.css +1 -1
  9. package/dist/elements/css/dialog.js +1 -1
  10. package/dist/elements/preupgrade/badge.css +1 -1
  11. package/dist/elements/preupgrade/bottom-app-bar.css +1 -1
  12. package/dist/elements/preupgrade/bottom-sheet.css +1 -1
  13. package/dist/elements/preupgrade/button-group.css +1 -1
  14. package/dist/elements/preupgrade/button.css +1 -1
  15. package/dist/elements/preupgrade/card.css +1 -1
  16. package/dist/elements/preupgrade/carousel.css +1 -1
  17. package/dist/elements/preupgrade/checkbox.css +1 -1
  18. package/dist/elements/preupgrade/chips.css +1 -1
  19. package/dist/elements/preupgrade/datepicker.css +1 -1
  20. package/dist/elements/preupgrade/dialog.css +1 -1
  21. package/dist/elements/preupgrade/divider.css +1 -1
  22. package/dist/elements/preupgrade/drawer.css +1 -1
  23. package/dist/elements/preupgrade/extended-fab.css +1 -1
  24. package/dist/elements/preupgrade/fab-menu.css +1 -1
  25. package/dist/elements/preupgrade/fab.css +1 -1
  26. package/dist/elements/preupgrade/icon-button.css +1 -1
  27. package/dist/elements/preupgrade/list.css +1 -1
  28. package/dist/elements/preupgrade/loading-indicator.css +1 -1
  29. package/dist/elements/preupgrade/menu.css +1 -1
  30. package/dist/elements/preupgrade/navigation-bar.css +1 -1
  31. package/dist/elements/preupgrade/navigation-rail.css +1 -1
  32. package/dist/elements/preupgrade/progress.css +1 -1
  33. package/dist/elements/preupgrade/radios.css +1 -1
  34. package/dist/elements/preupgrade/search.css +1 -1
  35. package/dist/elements/preupgrade/select.css +1 -1
  36. package/dist/elements/preupgrade/side-sheet.css +1 -1
  37. package/dist/elements/preupgrade/slider.css +1 -1
  38. package/dist/elements/preupgrade/snackbar.css +1 -1
  39. package/dist/elements/preupgrade/split-button.css +1 -1
  40. package/dist/elements/preupgrade/switch.css +1 -1
  41. package/dist/elements/preupgrade/tabs.css +1 -1
  42. package/dist/elements/preupgrade/text-field.css +1 -1
  43. package/dist/elements/preupgrade/timepicker.css +1 -1
  44. package/dist/elements/preupgrade/toolbar.css +1 -1
  45. package/dist/elements/preupgrade/tooltip.css +1 -1
  46. package/dist/elements/preupgrade/top-app-bar.css +1 -1
  47. package/dist/elements/preupgrade.css +1 -1
  48. package/dist/package.json +1 -1
  49. package/dist/styles/badge.css +1 -1
  50. package/dist/styles/base.css +1 -1
  51. package/dist/styles/bottom-app-bar.css +1 -1
  52. package/dist/styles/bottom-sheet.css +1 -1
  53. package/dist/styles/button-group.css +1 -1
  54. package/dist/styles/button.css +1 -1
  55. package/dist/styles/card.css +1 -1
  56. package/dist/styles/carousel.css +1 -1
  57. package/dist/styles/checkbox.css +1 -1
  58. package/dist/styles/chips.css +1 -1
  59. package/dist/styles/contrast.css +1 -1
  60. package/dist/styles/datepicker.css +1 -1
  61. package/dist/styles/dialog.css +2 -2
  62. package/dist/styles/divider.css +1 -1
  63. package/dist/styles/drawer.css +1 -1
  64. package/dist/styles/extended-fab.css +1 -1
  65. package/dist/styles/fab-menu.css +1 -1
  66. package/dist/styles/fab.css +1 -1
  67. package/dist/styles/icon-button.css +1 -1
  68. package/dist/styles/list.css +1 -1
  69. package/dist/styles/loading-indicator.css +1 -1
  70. package/dist/styles/menu.css +1 -1
  71. package/dist/styles/navigation-bar.css +1 -1
  72. package/dist/styles/navigation-rail.css +1 -1
  73. package/dist/styles/progress.css +1 -1
  74. package/dist/styles/radios.css +1 -1
  75. package/dist/styles/search.css +1 -1
  76. package/dist/styles/select.css +1 -1
  77. package/dist/styles/side-sheet.css +1 -1
  78. package/dist/styles/slider.css +1 -1
  79. package/dist/styles/snackbar.css +1 -1
  80. package/dist/styles/split-button.css +1 -1
  81. package/dist/styles/switch.css +1 -1
  82. package/dist/styles/tabs.css +1 -1
  83. package/dist/styles/text-field.css +1 -1
  84. package/dist/styles/timepicker.css +1 -1
  85. package/dist/styles/toolbar.css +1 -1
  86. package/dist/styles/tooltip.css +1 -1
  87. package/dist/styles/top-app-bar.css +1 -1
  88. package/dist/styles/typography.css +1 -1
  89. package/dist/styles/utilities.css +1 -1
  90. package/dist/styles.css +2 -2
  91. package/dist/themes/autumn-contrast.css +1 -1
  92. package/dist/themes/autumn.css +1 -1
  93. package/dist/themes/baseline-contrast.css +1 -1
  94. package/dist/themes/baseline.css +1 -1
  95. package/dist/themes/brownbeige-contrast.css +1 -1
  96. package/dist/themes/brownbeige.css +1 -1
  97. package/dist/themes/content-contrast.css +1 -1
  98. package/dist/themes/content.css +1 -1
  99. package/dist/themes/desert-contrast.css +1 -1
  100. package/dist/themes/desert.css +1 -1
  101. package/dist/themes/expressive-contrast.css +1 -1
  102. package/dist/themes/expressive.css +1 -1
  103. package/dist/themes/fidelity-contrast.css +1 -1
  104. package/dist/themes/fidelity.css +1 -1
  105. package/dist/themes/forest-contrast.css +1 -1
  106. package/dist/themes/forest.css +1 -1
  107. package/dist/themes/fruit-salad-contrast.css +1 -1
  108. package/dist/themes/fruit-salad.css +1 -1
  109. package/dist/themes/highcontrast-contrast.css +1 -1
  110. package/dist/themes/highcontrast.css +1 -1
  111. package/dist/themes/monochrome-contrast.css +1 -1
  112. package/dist/themes/monochrome.css +1 -1
  113. package/dist/themes/neutral-contrast.css +1 -1
  114. package/dist/themes/neutral.css +1 -1
  115. package/dist/themes/ocean-contrast.css +1 -1
  116. package/dist/themes/ocean.css +1 -1
  117. package/dist/themes/rainbow-contrast.css +1 -1
  118. package/dist/themes/rainbow.css +1 -1
  119. package/dist/themes/sageivory-contrast.css +1 -1
  120. package/dist/themes/sageivory.css +1 -1
  121. package/dist/themes/spring-contrast.css +1 -1
  122. package/dist/themes/spring.css +1 -1
  123. package/dist/themes/summer-contrast.css +1 -1
  124. package/dist/themes/summer.css +1 -1
  125. package/dist/themes/sunset-contrast.css +1 -1
  126. package/dist/themes/sunset.css +1 -1
  127. package/dist/themes/tealcaramel-contrast.css +1 -1
  128. package/dist/themes/tealcaramel.css +1 -1
  129. package/dist/themes/vibrant-contrast.css +1 -1
  130. package/dist/themes/vibrant.css +1 -1
  131. package/package.json +1 -1
  132. package/src/styles/components/_dialog.scss +8 -0
package/README.md CHANGED
@@ -94,7 +94,7 @@ What a bundler keeps, minified and gzipped (kB is 1,000 bytes), measured from th
94
94
  | `createButton` from `material`, with code splitting: the initial chunks (the progress indicator loads on demand) | 7.6 kB |
95
95
  | `createButton`, without code splitting (the progress indicator included) | 13.9 kB |
96
96
  | `createButton`, `createTextField` and `createCheckbox`, without code splitting | 20.4 kB |
97
- | Everything the root exports (`export * from 'material'`), without code splitting | 127.4 kB |
97
+ | Everything the root exports (`export * from 'material'`), without code splitting | 127.3 kB |
98
98
  | `material/styles/base` | 2.9 kB |
99
99
  | `material/styles/base` and `material/styles/button` | 5.2 kB |
100
100
  | `material/styles`, the full stylesheet | 63.7 kB |
@@ -1,113 +1,113 @@
1
1
  import { removeClass } from "../../core/dom/classes.js";
2
2
  import { hideFromTopLayer } from "../../core/dom/layer.js";
3
- export const withAPI = (options) => (component) => ({
4
- ...component,
5
- open() {
6
- options.visibility.open();
7
- return this;
8
- },
9
- close() {
10
- options.visibility.close();
11
- return this;
12
- },
13
- toggle(open) {
14
- options.visibility.toggle(open);
15
- return this;
16
- },
17
- isOpen() {
18
- return options.visibility.isOpen();
19
- },
20
- setTitle(title) {
21
- options.content.setTitle(title);
22
- return this;
23
- },
24
- getTitle() {
25
- return options.content.getTitle();
26
- },
27
- setSubtitle(subtitle) {
28
- options.content.setSubtitle(subtitle);
29
- return this;
30
- },
31
- getSubtitle() {
32
- return options.content.getSubtitle();
33
- },
34
- setContent(content) {
35
- options.content.setContent(content);
36
- return this;
37
- },
38
- getContent() {
39
- return options.content.getContent();
40
- },
41
- addButton(button) {
42
- options.buttons.addButton(button);
43
- return this;
44
- },
45
- removeButton(indexOrText) {
46
- options.buttons.removeButton(indexOrText);
47
- return this;
48
- },
49
- getButtons() {
50
- return options.buttons.getButtons();
51
- },
52
- setFooterAlignment(alignment) {
53
- options.buttons.setFooterAlignment(alignment);
54
- return this;
55
- },
56
- toggleDivider(show) {
57
- options.divider.toggleDivider(show);
58
- return this;
59
- },
60
- hasDivider() {
61
- return options.divider.hasDivider();
62
- },
63
- setSize(size) {
64
- options.size.setSize(size);
65
- return this;
66
- },
67
- on(event, handler) {
68
- options.events.on(event, handler);
69
- return this;
70
- },
71
- off(event, handler) {
72
- options.events.off(event, handler);
73
- return this;
74
- },
75
- getHeaderElement() {
76
- return options.content.getHeaderElement();
77
- },
78
- getContentElement() {
79
- return options.content.getContentElement();
80
- },
81
- getFooterElement() {
82
- return options.content.getFooterElement();
83
- },
84
- confirm(options) {
85
- if (component.confirm) {
3
+ export const withAPI = (component) => {
4
+ const options = component;
5
+ return {
6
+ ...component,
7
+ open() {
8
+ options.visibility.open();
9
+ return this;
10
+ },
11
+ close() {
12
+ options.visibility.close();
13
+ return this;
14
+ },
15
+ toggle(open) {
16
+ options.visibility.toggle(open);
17
+ return this;
18
+ },
19
+ isOpen() {
20
+ return options.visibility.isOpen();
21
+ },
22
+ setTitle(title) {
23
+ options.content.setTitle(title);
24
+ return this;
25
+ },
26
+ getTitle() {
27
+ return options.content.getTitle();
28
+ },
29
+ setSubtitle(subtitle) {
30
+ options.content.setSubtitle(subtitle);
31
+ return this;
32
+ },
33
+ getSubtitle() {
34
+ return options.content.getSubtitle();
35
+ },
36
+ setContent(content) {
37
+ options.content.setContent(content);
38
+ return this;
39
+ },
40
+ getContent() {
41
+ return options.content.getContent();
42
+ },
43
+ addButton(button) {
44
+ options.buttons.addButton(button);
45
+ return this;
46
+ },
47
+ removeButton(indexOrText) {
48
+ options.buttons.removeButton(indexOrText);
49
+ return this;
50
+ },
51
+ getButtons() {
52
+ return options.buttons.getButtons();
53
+ },
54
+ setFooterAlignment(alignment) {
55
+ options.buttons.setFooterAlignment(alignment);
56
+ return this;
57
+ },
58
+ toggleDivider(show) {
59
+ options.divider.toggleDivider(show);
60
+ return this;
61
+ },
62
+ hasDivider() {
63
+ return options.divider.hasDivider();
64
+ },
65
+ setSize(size) {
66
+ options.size.setSize(size);
67
+ return this;
68
+ },
69
+ on(event, handler) {
70
+ options.on(event, handler);
71
+ return this;
72
+ },
73
+ off(event, handler) {
74
+ options.off(event, handler);
75
+ return this;
76
+ },
77
+ getHeaderElement() {
78
+ return options.content.getHeaderElement();
79
+ },
80
+ getContentElement() {
81
+ return options.content.getContentElement();
82
+ },
83
+ getFooterElement() {
84
+ return options.content.getFooterElement();
85
+ },
86
+ confirm(options) {
86
87
  return component.confirm(options || { message: "Are you sure?" });
87
- }
88
- return Promise.resolve(false);
89
- },
90
- destroy() {
91
- const wasOpen = this.isOpen();
92
- options.visibility.cancel();
93
- if (wasOpen) {
94
- const dialogVisibleClass = `${component.getClass("dialog")}--visible`;
95
- const overlayVisibleClass = `${component.getClass("dialog__overlay")}--visible`;
96
- removeClass(component.element, dialogVisibleClass);
97
- removeClass(component.overlay, overlayVisibleClass);
98
- hideFromTopLayer(component.element);
99
- if (options.focus && options.focus.releaseFocus) {
100
- options.focus.releaseFocus();
88
+ },
89
+ destroy() {
90
+ const wasOpen = this.isOpen();
91
+ options.visibility.cancel();
92
+ if (wasOpen) {
93
+ const dialogVisibleClass = `${component.getClass("dialog")}--visible`;
94
+ const overlayVisibleClass = `${component.getClass("dialog__overlay")}--visible`;
95
+ removeClass(component.element, dialogVisibleClass);
96
+ removeClass(component.overlay, overlayVisibleClass);
97
+ hideFromTopLayer(component.element);
98
+ if (options.focus && options.focus.releaseFocus) {
99
+ options.focus.releaseFocus();
100
+ }
101
101
  }
102
- }
103
- options.lifecycle.destroy();
104
- if (component.overlay && component.overlay.parentNode) {
105
- component.overlay.parentNode.removeChild(component.overlay);
106
- }
107
- if (component.element.parentNode !== component.overlay) {
108
- component.element.remove();
109
- }
110
- },
111
- element: component.element,
112
- overlay: component.overlay,
113
- });
102
+ options.lifecycle.destroy();
103
+ if (component.overlay && component.overlay.parentNode) {
104
+ component.overlay.parentNode.removeChild(component.overlay);
105
+ }
106
+ if (component.element.parentNode !== component.overlay) {
107
+ component.element.remove();
108
+ }
109
+ },
110
+ element: component.element,
111
+ overlay: component.overlay,
112
+ };
113
+ };
@@ -35,56 +35,4 @@ export const getElementConfig = (config) => {
35
35
  className: config.class
36
36
  });
37
37
  };
38
- export const getOverlayConfig = () => {
39
- return {
40
- tag: 'div',
41
- attributes: {},
42
- className: ''
43
- };
44
- };
45
- export const getApiConfig = (comp) => ({
46
- visibility: {
47
- open: () => comp.visibility.open(),
48
- close: () => comp.visibility.close(),
49
- toggle: (visible) => comp.visibility.toggle(visible),
50
- isOpen: () => comp.visibility.isOpen(),
51
- cancel: () => comp.visibility.cancel()
52
- },
53
- content: {
54
- setTitle: (title) => comp.content.setTitle(title),
55
- getTitle: () => comp.content.getTitle(),
56
- setSubtitle: (subtitle) => comp.content.setSubtitle(subtitle),
57
- getSubtitle: () => comp.content.getSubtitle(),
58
- setContent: (content) => comp.content.setContent(content),
59
- getContent: () => comp.content.getContent(),
60
- getHeaderElement: () => comp.content.getHeaderElement(),
61
- getContentElement: () => comp.content.getContentElement(),
62
- getFooterElement: () => comp.content.getFooterElement()
63
- },
64
- buttons: {
65
- addButton: (button) => comp.buttons.addButton(button),
66
- removeButton: (indexOrText) => comp.buttons.removeButton(indexOrText),
67
- getButtons: () => comp.buttons.getButtons(),
68
- setFooterAlignment: (alignment) => comp.buttons.setFooterAlignment(alignment)
69
- },
70
- focus: {
71
- trapFocus: () => comp.focus.trapFocus(),
72
- releaseFocus: () => comp.focus.releaseFocus()
73
- },
74
- size: {
75
- setSize: (size) => comp.size.setSize(size)
76
- },
77
- divider: {
78
- toggleDivider: (show) => comp.divider.toggleDivider(show),
79
- hasDivider: () => comp.divider.hasDivider()
80
- },
81
- events: {
82
- on: (event, handler) => comp.on(event, handler),
83
- off: (event, handler) => comp.off(event, handler),
84
- trigger: (event, data) => comp.emit(event, data)
85
- },
86
- lifecycle: {
87
- destroy: () => comp.lifecycle.destroy()
88
- }
89
- });
90
38
  export default defaultConfig;
@@ -3,11 +3,11 @@ import { createBase, withElement } from "../../core/compose/component.js";
3
3
  import { withEvents, withLifecycle } from "../../core/compose/features/index.js";
4
4
  import { withStructure, withVisibility, withContent, withButtons, withSize, withConfirm, withDivider } from "./features.js";
5
5
  import { withAPI } from "./api.js";
6
- import { createBaseConfig, getElementConfig, getApiConfig } from "./config.js";
6
+ import { createBaseConfig, getElementConfig } from "./config.js";
7
7
  const createDialog = (config = {}) => {
8
8
  const baseConfig = createBaseConfig(config);
9
9
  try {
10
- const dialog = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)), withStructure(baseConfig, () => dialog), withVisibility(() => dialog), withContent(), withButtons(() => dialog), withSize(), withDivider(), withConfirm(), withLifecycle(), comp => withAPI(getApiConfig(comp))(comp))(baseConfig);
10
+ const dialog = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)), withStructure(baseConfig, () => dialog), withVisibility(() => dialog), withContent(), withButtons(() => dialog), withSize(), withDivider(), withConfirm(), withLifecycle(), withAPI)(baseConfig);
11
11
  if (baseConfig.on) {
12
12
  Object.entries(baseConfig.on).forEach(([event, handler]) => {
13
13
  if (typeof handler === 'function') {
@@ -1,9 +1,7 @@
1
- import { getOverlayConfig } from "./config.js";
2
1
  import createButton from "../button/index.js";
3
- import { createDivider } from "../divider/index.js";
4
2
  import { addClass, removeClass } from "../../core/dom/classes.js";
5
3
  import { setHTML } from "../../core/dom/html.js";
6
- import { activeElementOf, deepActiveElement, tabStops, wrapTab } from "../../core/dom/focus.js";
4
+ import { deepActiveElement, tabStops, wrapTab } from "../../core/dom/focus.js";
7
5
  import { hideFromTopLayer, onModalEscape, onTopLayerClose, showInTopLayer } from "../../core/dom/layer.js";
8
6
  const DIALOG_EVENTS = {
9
7
  OPEN: "open",
@@ -18,17 +16,8 @@ export const withStructure = (config, getComponent) => (component) => {
18
16
  const uid = `${component.getClass("dialog")}-${++dialogCount}`;
19
17
  const titleId = `${uid}-title`;
20
18
  const contentId = `${uid}-content`;
21
- const overlayConfig = getOverlayConfig();
22
- const overlay = document.createElement(overlayConfig.tag || "div");
19
+ const overlay = document.createElement("div");
23
20
  overlay.classList.add(component.getClass("dialog__overlay"));
24
- if (overlayConfig.attributes &&
25
- typeof overlayConfig.attributes === "object") {
26
- Object.entries(overlayConfig.attributes).forEach(([key, value]) => {
27
- if (key && typeof key === "string" && value !== undefined) {
28
- overlay.setAttribute(key, String(value));
29
- }
30
- });
31
- }
32
21
  if (config.zIndex) {
33
22
  overlay.style.zIndex = String(config.zIndex);
34
23
  }
@@ -95,10 +84,8 @@ export const withStructure = (config, getComponent) => (component) => {
95
84
  return footer;
96
85
  };
97
86
  const createDividerElement = () => {
98
- const divider = createDivider({
99
- variant: "full-width",
100
- class: component.getClass("dialog__divider"),
101
- });
87
+ const divider = document.createElement("hr");
88
+ divider.classList.add(component.getClass("dialog__divider"));
102
89
  return divider;
103
90
  };
104
91
  const header = createHeader();
@@ -129,16 +116,16 @@ export const withStructure = (config, getComponent) => (component) => {
129
116
  let footerDivider = null;
130
117
  if (config.divider) {
131
118
  headerDivider = createDividerElement();
132
- headerDivider.element.classList.add(component.getClass("dialog__header-divider"));
133
- component.element.appendChild(headerDivider.element);
119
+ headerDivider.classList.add(component.getClass("dialog__header-divider"));
120
+ component.element.appendChild(headerDivider);
134
121
  if (footer) {
135
122
  footerDivider = createDividerElement();
136
- footerDivider.element.classList.add(component.getClass("dialog__footer-divider"));
123
+ footerDivider.classList.add(component.getClass("dialog__footer-divider"));
137
124
  }
138
125
  }
139
126
  component.element.appendChild(content);
140
127
  if (footerDivider) {
141
- component.element.appendChild(footerDivider.element);
128
+ component.element.appendChild(footerDivider);
142
129
  }
143
130
  if (footer) {
144
131
  component.element.appendChild(footer);
@@ -170,29 +157,25 @@ export const withDivider = () => (component) => {
170
157
  divider: {
171
158
  toggleDivider(show) {
172
159
  if (show && !component.structure.headerDivider) {
173
- const headerDivider = createDivider({
174
- variant: "full-width",
175
- class: `${component.getClass("dialog__divider")} ${component.getClass("dialog__header-divider")}`,
176
- });
177
- component.element.insertBefore(headerDivider.element, component.structure.content);
160
+ const headerDivider = document.createElement("hr");
161
+ headerDivider.classList.add(component.getClass("dialog__divider"), component.getClass("dialog__header-divider"));
162
+ component.element.insertBefore(headerDivider, component.structure.content);
178
163
  component.structure.headerDivider = headerDivider;
179
164
  if (component.structure.footer &&
180
165
  !component.structure.footerDivider) {
181
- const footerDivider = createDivider({
182
- variant: "full-width",
183
- class: `${component.getClass("dialog__divider")} ${component.getClass("dialog__footer-divider")}`,
184
- });
185
- component.element.insertBefore(footerDivider.element, component.structure.footer);
166
+ const footerDivider = document.createElement("hr");
167
+ footerDivider.classList.add(component.getClass("dialog__divider"), component.getClass("dialog__footer-divider"));
168
+ component.element.insertBefore(footerDivider, component.structure.footer);
186
169
  component.structure.footerDivider = footerDivider;
187
170
  }
188
171
  }
189
172
  else if (!show) {
190
173
  if (component.structure.headerDivider) {
191
- component.structure.headerDivider.element.remove();
174
+ component.structure.headerDivider.remove();
192
175
  component.structure.headerDivider = null;
193
176
  }
194
177
  if (component.structure.footerDivider) {
195
- component.structure.footerDivider.element.remove();
178
+ component.structure.footerDivider.remove();
196
179
  component.structure.footerDivider = null;
197
180
  }
198
181
  }
@@ -251,39 +234,42 @@ export const withVisibility = (getComponent) => (component) => {
251
234
  let showTimer;
252
235
  let afterOpenTimer;
253
236
  let afterCloseTimer;
237
+ let afterOpenWaiting = false;
254
238
  const openDuration = component.config.animationDuration ?? 500;
255
239
  const closeDuration = component.config.animationDuration ?? 150;
256
240
  const top = component.config.layer === "top";
257
241
  const scrim = top ? component.element : component.overlay;
258
- const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
259
242
  let previouslyFocusedElement = null;
260
243
  let mouseDownOnOverlay = false;
261
244
  const inerted = [];
262
245
  let scrollLock = null;
263
- const focusable = () => Array.from(component.element.querySelectorAll(focusableElements)).filter((el) => !el.hasAttribute("disabled") &&
264
- el.getAttribute("aria-hidden") !== "true" &&
265
- (el.offsetWidth > 0 || el.offsetHeight > 0 || el === activeElementOf(el)));
266
- function handleTabKey(e) {
267
- if (e.key !== "Tab")
246
+ let focusFrame;
247
+ let focusView;
248
+ let focusRequest = 0;
249
+ let focusPending = false;
250
+ const cancelFocusFrame = () => {
251
+ focusRequest += 1;
252
+ focusPending = false;
253
+ if (focusFrame !== undefined) {
254
+ focusView?.cancelAnimationFrame(focusFrame);
255
+ focusFrame = undefined;
256
+ focusView = undefined;
257
+ }
258
+ };
259
+ const emitAfterOpen = () => {
260
+ afterOpenWaiting = false;
261
+ if (!opened || typeof component.emit !== "function")
268
262
  return;
269
- const elements = focusable();
270
- if (elements.length === 0) {
271
- e.preventDefault();
272
- component.element.focus();
263
+ component.emit(DIALOG_EVENTS.AFTER_OPEN, { dialog: getComponent() });
264
+ };
265
+ const afterOpenDue = () => {
266
+ afterOpenTimer = undefined;
267
+ if (focusPending) {
268
+ afterOpenWaiting = true;
273
269
  return;
274
270
  }
275
- const first = elements[0];
276
- const last = elements[elements.length - 1];
277
- const active = activeElementOf(component.element);
278
- if (e.shiftKey && (active === first || !component.element.contains(active))) {
279
- last.focus();
280
- e.preventDefault();
281
- }
282
- else if (!e.shiftKey && active === last) {
283
- first.focus();
284
- e.preventDefault();
285
- }
286
- }
271
+ emitAfterOpen();
272
+ };
287
273
  const inertBackground = () => {
288
274
  if (component.config.modal === false)
289
275
  return;
@@ -313,20 +299,76 @@ export const withVisibility = (getComponent) => (component) => {
313
299
  scrollLock = null;
314
300
  }
315
301
  };
302
+ let containerPosition = null;
303
+ const unscopeScrim = () => {
304
+ removeClass(component.overlay, `${component.getClass("dialog__overlay")}--contained`);
305
+ if (containerPosition === null)
306
+ return;
307
+ component.structure.container.style.position = containerPosition;
308
+ containerPosition = null;
309
+ };
310
+ const scopeScrim = () => {
311
+ const container = component.structure.container;
312
+ if (top || container === document.body)
313
+ return;
314
+ if (getComputedStyle(container).position === "static") {
315
+ containerPosition ?? (containerPosition = container.style.position);
316
+ container.style.position = "relative";
317
+ }
318
+ addClass(component.overlay, `${component.getClass("dialog__overlay")}--contained`);
319
+ if (!container.clientHeight ||
320
+ container.clientWidth < component.element.offsetWidth ||
321
+ container.clientHeight < component.element.offsetHeight) {
322
+ unscopeScrim();
323
+ }
324
+ };
316
325
  const handleWrap = (e) => wrapTab(component.element, e);
326
+ const focusWhenVisible = (target) => {
327
+ const view = target.ownerDocument.defaultView;
328
+ if (!view || typeof view.requestAnimationFrame !== "function") {
329
+ target.focus();
330
+ focusPending = false;
331
+ if (afterOpenWaiting)
332
+ emitAfterOpen();
333
+ return;
334
+ }
335
+ cancelFocusFrame();
336
+ const request = focusRequest;
337
+ const settle = () => {
338
+ focusFrame = undefined;
339
+ if (request !== focusRequest)
340
+ return;
341
+ if (!opened || !target.isConnected) {
342
+ focusPending = false;
343
+ if (afterOpenWaiting)
344
+ emitAfterOpen();
345
+ return;
346
+ }
347
+ if (view.getComputedStyle(target).visibility !== "hidden") {
348
+ target.focus();
349
+ focusPending = false;
350
+ if (afterOpenWaiting)
351
+ emitAfterOpen();
352
+ return;
353
+ }
354
+ focusPending = true;
355
+ focusFrame = view.requestAnimationFrame(settle);
356
+ focusView = view;
357
+ };
358
+ settle();
359
+ };
317
360
  const trapFocus = () => {
318
361
  inertBackground();
319
362
  if (component.config.autofocus !== false) {
320
363
  const requested = component.element.querySelector("[autofocus]");
321
- const target = requested || (top ? tabStops(component.element) : focusable())[0] || component.element;
322
- target.focus();
364
+ const target = requested || tabStops(component.element)[0] || component.element;
365
+ focusWhenVisible(target);
323
366
  }
324
367
  if (component.config.trapFocus !== false) {
325
- component.element.addEventListener("keydown", top ? handleWrap : handleTabKey);
368
+ component.element.addEventListener("keydown", handleWrap);
326
369
  }
327
370
  };
328
371
  const releaseFocus = () => {
329
- component.element.removeEventListener("keydown", handleTabKey);
330
372
  component.element.removeEventListener("keydown", handleWrap);
331
373
  releaseBackground();
332
374
  if (previouslyFocusedElement) {
@@ -390,6 +432,7 @@ export const withVisibility = (getComponent) => (component) => {
390
432
  component.element.setAttribute("open", "");
391
433
  }
392
434
  };
435
+ scopeScrim();
393
436
  if (isOpen) {
394
437
  if (top)
395
438
  showModal();
@@ -417,6 +460,8 @@ export const withVisibility = (getComponent) => (component) => {
417
460
  return;
418
461
  opened = true;
419
462
  clearTimeout(afterCloseTimer);
463
+ afterOpenWaiting = false;
464
+ focusPending = false;
420
465
  if (top) {
421
466
  showModal();
422
467
  void component.element.offsetWidth;
@@ -426,9 +471,7 @@ export const withVisibility = (getComponent) => (component) => {
426
471
  component.emit(DIALOG_EVENTS.OPEN, { dialog: getComponent() });
427
472
  if (!opened)
428
473
  return;
429
- afterOpenTimer = setTimeout(() => {
430
- component.emit(DIALOG_EVENTS.AFTER_OPEN, { dialog: getComponent() });
431
- }, openDuration);
474
+ afterOpenTimer = setTimeout(afterOpenDue, openDuration);
432
475
  return;
433
476
  }
434
477
  if (component.element.parentNode !== component.overlay) {
@@ -438,6 +481,7 @@ export const withVisibility = (getComponent) => (component) => {
438
481
  const container = component.structure.container || document.body;
439
482
  container.appendChild(component.overlay);
440
483
  }
484
+ scopeScrim();
441
485
  setupEvents();
442
486
  if (typeof component.emit === "function") {
443
487
  component.emit(DIALOG_EVENTS.OPEN, { dialog: getComponent() });
@@ -449,9 +493,7 @@ export const withVisibility = (getComponent) => (component) => {
449
493
  addClass(component.element, `${component.getClass("dialog")}--visible`);
450
494
  trapFocus();
451
495
  if (typeof component.emit === "function") {
452
- afterOpenTimer = setTimeout(() => {
453
- component.emit(DIALOG_EVENTS.AFTER_OPEN, { dialog: getComponent() });
454
- }, openDuration);
496
+ afterOpenTimer = setTimeout(afterOpenDue, openDuration);
455
497
  }
456
498
  }, 10);
457
499
  },
@@ -474,6 +516,8 @@ export const withVisibility = (getComponent) => (component) => {
474
516
  opened = false;
475
517
  clearTimeout(showTimer);
476
518
  clearTimeout(afterOpenTimer);
519
+ afterOpenWaiting = false;
520
+ cancelFocusFrame();
477
521
  const dialogVisibleClass = `${component.getClass("dialog")}--visible`;
478
522
  const overlayVisibleClass = `${component.getClass("dialog__overlay")}--visible`;
479
523
  removeClass(component.element, dialogVisibleClass);
@@ -491,6 +535,7 @@ export const withVisibility = (getComponent) => (component) => {
491
535
  if (!top && component.overlay && component.overlay.parentNode) {
492
536
  component.overlay.parentNode.removeChild(component.overlay);
493
537
  }
538
+ unscopeScrim();
494
539
  if (typeof component.emit === "function") {
495
540
  component.emit(DIALOG_EVENTS.AFTER_CLOSE, { dialog: getComponent() });
496
541
  }
@@ -515,7 +560,10 @@ export const withVisibility = (getComponent) => (component) => {
515
560
  clearTimeout(showTimer);
516
561
  clearTimeout(afterOpenTimer);
517
562
  clearTimeout(afterCloseTimer);
563
+ afterOpenWaiting = false;
564
+ cancelFocusFrame();
518
565
  cleanupEvents();
566
+ unscopeScrim();
519
567
  },
520
568
  };
521
569
  if (component && component.on) {
@@ -1,5 +1,4 @@
1
1
  import type { ButtonComponent } from "../button/types.js";
2
- import type { DividerComponent } from "../divider/types.js";
3
2
  import type { EventCallback } from "../../core/state/emitter.js";
4
3
  export type DialogSize = 'small' | 'medium' | 'large' | 'fullwidth' | 'fullscreen';
5
4
  export type DialogAnimation = 'scale' | 'slide-up' | 'slide-down' | 'fade';
@@ -51,8 +50,8 @@ export interface DialogStructure {
51
50
  header: HTMLElement;
52
51
  content: HTMLElement;
53
52
  footer: HTMLElement | null;
54
- headerDivider: DividerComponent | null;
55
- footerDivider: DividerComponent | null;
53
+ headerDivider: HTMLElement | null;
54
+ footerDivider: HTMLElement | null;
56
55
  container: HTMLElement;
57
56
  }
58
57
  export interface DialogButtonRecord {
@@ -10,8 +10,8 @@ export const deepActiveElement = () => {
10
10
  };
11
11
  const isTabStop = (element) => element.tabIndex !== undefined &&
12
12
  element.tabIndex >= 0 &&
13
- !element.matches(":disabled, [inert] *, [inert]") &&
14
- element.getClientRects().length > 0;
13
+ !element.matches(":disabled, [inert] *, [inert], [aria-hidden='true']") &&
14
+ (element.offsetWidth > 0 || element.getClientRects().length > 0);
15
15
  export const tabStops = (container) => {
16
16
  const found = [];
17
17
  const visit = (element) => {