@douyinfe/semi-foundation 2.11.1 → 2.12.0-beta.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.
@@ -294,7 +294,8 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
294
294
  }
295
295
 
296
296
  handleUpClick(event: any) {
297
- if (!this._isMouseButtonLeft(event)) {
297
+ const { readonly } = this.getProps();
298
+ if (!this._isMouseButtonLeft(event) || readonly) {
298
299
  return;
299
300
  }
300
301
  this._adapter.setClickUpOrDown(true);
@@ -314,7 +315,8 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
314
315
  }
315
316
 
316
317
  handleDownClick(event: any) {
317
- if (!this._isMouseButtonLeft(event)) {
318
+ const { readonly } = this.getProps();
319
+ if (!this._isMouseButtonLeft(event) || readonly) {
318
320
  return;
319
321
  }
320
322
  this._adapter.setClickUpOrDown(true);
@@ -321,7 +321,11 @@ class InputNumberFoundation extends _foundation.default {
321
321
  }
322
322
 
323
323
  handleUpClick(event) {
324
- if (!this._isMouseButtonLeft(event)) {
324
+ const {
325
+ readonly
326
+ } = this.getProps();
327
+
328
+ if (!this._isMouseButtonLeft(event) || readonly) {
325
329
  return;
326
330
  }
327
331
 
@@ -344,7 +348,11 @@ class InputNumberFoundation extends _foundation.default {
344
348
  }
345
349
 
346
350
  handleDownClick(event) {
347
- if (!this._isMouseButtonLeft(event)) {
351
+ const {
352
+ readonly
353
+ } = this.getProps();
354
+
355
+ if (!this._isMouseButtonLeft(event) || readonly) {
348
356
  return;
349
357
  }
350
358
 
@@ -19,5 +19,10 @@ declare class TabsFoundation<P = Record<string, any>, S = Record<string, any>> e
19
19
  handleTabListChange(): void;
20
20
  handleTabPanesChange(): void;
21
21
  handleTabDelete(tabKey: string): void;
22
+ handlePrevent: (event: any) => void;
23
+ handleKeyDown: (event: any, itemKey: string, closable: boolean) => void;
24
+ determineOrientation(event: any, tabs: HTMLElement[]): void;
25
+ handleDeleteKeyDown(event: any, tabs: HTMLElement[], itemKey: string, closable: boolean): void;
26
+ switchTabOnArrowPress(event: any, tabs: HTMLElement[]): void;
22
27
  }
23
28
  export default TabsFoundation;
@@ -12,14 +12,69 @@ exports.default = void 0;
12
12
 
13
13
  var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign"));
14
14
 
15
+ var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
16
+
17
+ var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
18
+
19
+ var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of"));
20
+
15
21
  var _noop2 = _interopRequireDefault(require("lodash/noop"));
16
22
 
23
+ var _get2 = _interopRequireDefault(require("lodash/get"));
24
+
17
25
  var _foundation = _interopRequireDefault(require("../base/foundation"));
18
26
 
19
27
  class TabsFoundation extends _foundation.default {
20
28
  constructor(adapter) {
21
29
  super((0, _assign.default)({}, adapter));
22
30
  this.destroy = _noop2.default;
31
+
32
+ this.handlePrevent = event => {
33
+ event.stopPropagation();
34
+ event.preventDefault();
35
+ };
36
+
37
+ this.handleKeyDown = (event, itemKey, closable) => {
38
+ var _context;
39
+
40
+ const tabs = (0, _filter.default)(_context = [...event.target.parentNode.childNodes]).call(_context, item => {
41
+ var _context2;
42
+
43
+ return (0, _includes.default)(_context2 = (0, _get2.default)(item, 'attributes.data-tabkey.value', '')).call(_context2, 'semiTab') && item.ariaDisabled !== "true";
44
+ });
45
+
46
+ switch (event.key) {
47
+ case "ArrowLeft":
48
+ case "ArrowRight":
49
+ case "ArrowUp":
50
+ case "ArrowDown":
51
+ this.determineOrientation(event, tabs);
52
+ break;
53
+
54
+ case "Backspace":
55
+ case "Delete":
56
+ this.handleDeleteKeyDown(event, tabs, itemKey, closable);
57
+ break;
58
+
59
+ case "Enter":
60
+ case " ":
61
+ this.handleTabClick(itemKey, event);
62
+ this.handlePrevent(event);
63
+ break;
64
+
65
+ case "Home":
66
+ tabs[0].focus(); // focus first tab
67
+
68
+ this.handlePrevent(event);
69
+ break;
70
+
71
+ case "End":
72
+ tabs[tabs.length - 1].focus(); // focus last tab
73
+
74
+ this.handlePrevent(event);
75
+ break;
76
+ }
77
+ };
23
78
  }
24
79
 
25
80
  init() {
@@ -89,6 +144,59 @@ class TabsFoundation extends _foundation.default {
89
144
  this._adapter.notifyTabDelete(tabKey);
90
145
  }
91
146
 
147
+ determineOrientation(event, tabs) {
148
+ const {
149
+ tabPosition
150
+ } = this.getProps();
151
+ const isVertical = tabPosition === 'left';
152
+
153
+ if (isVertical) {
154
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
155
+ this.switchTabOnArrowPress(event, tabs);
156
+ this.handlePrevent(event);
157
+ }
158
+ } else {
159
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
160
+ this.switchTabOnArrowPress(event, tabs);
161
+ this.handlePrevent(event);
162
+ }
163
+ }
164
+ }
165
+
166
+ handleDeleteKeyDown(event, tabs, itemKey, closable) {
167
+ if (closable) {
168
+ this.handleTabDelete(itemKey);
169
+ const index = (0, _indexOf.default)(tabs).call(tabs, event.target); // Move focus to next element after deletion
170
+ // If the element is the last removable tab, focus to its previous tab
171
+
172
+ if (tabs.length !== 1) {
173
+ tabs[index + 1 >= tabs.length ? index - 1 : index + 1].focus();
174
+ }
175
+ }
176
+ }
177
+
178
+ switchTabOnArrowPress(event, tabs) {
179
+ const index = (0, _indexOf.default)(tabs).call(tabs, event.target);
180
+ const direction = {
181
+ "ArrowLeft": -1,
182
+ "ArrowUp": -1,
183
+ "ArrowRight": 1,
184
+ "ArrowDown": 1
185
+ };
186
+
187
+ if (direction[event.key]) {
188
+ if (index !== undefined) {
189
+ if (tabs[index + direction[event.key]]) {
190
+ tabs[index + direction[event.key]].focus();
191
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
192
+ tabs[tabs.length - 1].focus(); // focus last tab
193
+ } else if (event.key === "ArrowRight" || event.key == "ArrowDown") {
194
+ tabs[0].focus(); // focus first tab
195
+ }
196
+ }
197
+ }
198
+ }
199
+
92
200
  }
93
201
 
94
202
  var _default = TabsFoundation;
@@ -101,6 +101,10 @@
101
101
  width: 0;
102
102
  height: 0;
103
103
  }
104
+ .semi-tabs-bar-collapse .semi-overflow-list .semi-overflow-list-scroll-wrapper:focus-visible {
105
+ outline: 2px solid var(--semi-color-primary-light-active);
106
+ outline-offset: -2px;
107
+ }
104
108
  .semi-tabs-bar-collapse .semi-tabs-bar-arrow-start {
105
109
  margin-right: 4px;
106
110
  }
@@ -130,6 +134,10 @@
130
134
  .semi-tabs-bar-line.semi-tabs-bar-top .semi-tabs-tab:hover {
131
135
  border-bottom: 2px solid var(--semi-color-fill-0);
132
136
  }
137
+ .semi-tabs-bar-line.semi-tabs-bar-top .semi-tabs-tab:focus-visible {
138
+ outline: 2px solid var(--semi-color-primary-light-active);
139
+ outline-offset: -1px;
140
+ }
133
141
  .semi-tabs-bar-line.semi-tabs-bar-top .semi-tabs-tab:active {
134
142
  border-bottom: 2px solid var(--semi-color-fill-1);
135
143
  }
@@ -156,6 +164,10 @@
156
164
  border-left: 2px solid var(--semi-color-fill-0);
157
165
  background-color: var(--semi-color-fill-0);
158
166
  }
167
+ .semi-tabs-bar-line.semi-tabs-bar-left .semi-tabs-tab:focus-visible {
168
+ outline: 2px solid var(--semi-color-primary-light-active);
169
+ outline-offset: -2px;
170
+ }
159
171
  .semi-tabs-bar-line.semi-tabs-bar-left .semi-tabs-tab:active {
160
172
  border-left: 2px solid var(--semi-color-fill-1);
161
173
  background-color: var(--semi-color-fill-1);
@@ -241,6 +253,10 @@
241
253
  .semi-tabs-bar-card .semi-tabs-tab:hover {
242
254
  background: var(--semi-color-fill-0);
243
255
  }
256
+ .semi-tabs-bar-card .semi-tabs-tab:focus-visible {
257
+ outline: 2px solid var(--semi-color-primary-light-active);
258
+ outline-offset: -2px;
259
+ }
244
260
  .semi-tabs-bar-card .semi-tabs-tab:active {
245
261
  background: var(--semi-color-fill-1);
246
262
  }
@@ -263,6 +279,10 @@
263
279
  border: none;
264
280
  background-color: var(--semi-color-fill-0);
265
281
  }
282
+ .semi-tabs-bar-button .semi-tabs-tab:focus-visible {
283
+ outline: 2px solid var(--semi-color-primary-light-active);
284
+ outline-offset: -2px;
285
+ }
266
286
  .semi-tabs-bar-button .semi-tabs-tab:active {
267
287
  background-color: var(--semi-color-fill-1);
268
288
  }
@@ -283,6 +303,9 @@
283
303
  width: 100%;
284
304
  overflow: hidden;
285
305
  }
306
+ .semi-tabs-pane:focus-visible {
307
+ outline: 2px solid var(--semi-color-primary-light-active);
308
+ }
286
309
  .semi-tabs-pane-inactive, .semi-tabs-content-no-animated .semi-tabs-pane-inactive {
287
310
  display: none;
288
311
  }
@@ -124,6 +124,11 @@ $module: #{$prefix}-tabs;
124
124
  width: 0;
125
125
  height: 0;
126
126
  }
127
+
128
+ &:focus-visible {
129
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
130
+ outline-offset: $width-tabs-outline-offset;
131
+ }
127
132
  }
128
133
  }
129
134
 
@@ -164,6 +169,11 @@ $module: #{$prefix}-tabs;
164
169
  &:hover {
165
170
  border-bottom: $width-tabs_bar_line_tab-border solid $color-tabs_tab_line_default-border-hover;
166
171
  }
172
+
173
+ &:focus-visible {
174
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
175
+ outline-offset: $width-tabs_bar_line-outline-offset;
176
+ }
167
177
 
168
178
  &:active {
169
179
  border-bottom: $width-tabs_bar_line_tab-border solid $color-tabs_tab_line_default-border-active;
@@ -202,6 +212,11 @@ $module: #{$prefix}-tabs;
202
212
  border-left: $width-tabs_bar_line_tab-border solid $color-tabs_tab_line_default-border-hover;
203
213
  background-color: $color-tabs_tab_line_vertical-bg-hover;
204
214
  }
215
+
216
+ &:focus-visible {
217
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
218
+ outline-offset: $width-tabs-outline-offset;
219
+ }
205
220
 
206
221
  &:active {
207
222
  border-left: $width-tabs_bar_line_tab-border solid $color-tabs_tab_line_default-border-active;
@@ -327,6 +342,11 @@ $module: #{$prefix}-tabs;
327
342
  &:hover {
328
343
  background: $color-tabs_tab_card-bg-hover;
329
344
  }
345
+
346
+ &:focus-visible {
347
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
348
+ outline-offset: $width-tabs-outline-offset;
349
+ }
330
350
 
331
351
  &:active {
332
352
  background: $color-tabs_tab_card-bg-active;
@@ -365,6 +385,11 @@ $module: #{$prefix}-tabs;
365
385
  border: none;
366
386
  background-color: $color-tabs_tab_button-bg-hover;
367
387
  }
388
+
389
+ &:focus-visible {
390
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
391
+ outline-offset: $width-tabs-outline-offset;
392
+ }
368
393
 
369
394
  &:active {
370
395
  background-color: $color-tabs_tab_button-bg-active;
@@ -393,13 +418,17 @@ $module: #{$prefix}-tabs;
393
418
  height: 100%;
394
419
  padding: $spacing-tabs_content_left-paddingY $spacing-tabs_content_left-paddingX;
395
420
  }
396
-
421
+
397
422
  &-pane {
398
423
  width: 100%;
399
424
  overflow: hidden;
400
425
  // position: absolute;
401
426
  // flex-shrink: 0;
402
427
  // position: absolute;
428
+
429
+ &:focus-visible {
430
+ outline: $width-tabs-outline solid $color-tabs_tab-outline-focus;
431
+ }
403
432
  }
404
433
 
405
434
  &-pane-inactive,
@@ -44,6 +44,8 @@ $color-tabs_tab-icon-hover: var(--semi-color-text-0); // 页签图标颜色 -
44
44
  $color-tabs_tab-icon-active: var(--semi-color-text-0); // 页签图标颜色 - 按下
45
45
  $color-tabs_tab_selected-icon-default: var(--semi-color-primary); // 页签图标颜色 - 选中
46
46
 
47
+ $color-tabs_tab-outline-focus: var(--semi-color-primary-light-active); // 页签轮廓 - 聚焦
48
+
47
49
 
48
50
  $font-tabs_tab-fontWeight: $font-weight-regular; // 页签文本字重 - 默认
49
51
  $font-tabs_tab_active-fontWeight: $font-weight-bold; // 页签文本字重 - 选中
@@ -54,6 +56,9 @@ $width-tabs_bar_line-border: $border-thickness-control; // 线条式页签底部
54
56
  $width-tabs_bar_line_tab-border: 2px; // 页签标示线宽度
55
57
 
56
58
  $width-tabs_bar_card-border: $border-thickness-control; // 卡片式页签底部分割线宽度
59
+ $width-tabs-outline: 2px; // 聚焦轮廓宽度
60
+ $width-tabs-outline-offset: -2px; // 聚焦轮廓偏移宽度
61
+ $width-tabs_bar_line-outline-offset: -1px; // 线条式页签聚焦轮廓偏移宽度
57
62
 
58
63
  $height-tabs_bar_extra_large: 50px; // 大尺寸页签高度
59
64
  $font-tabs_bar_extra_large-lineHeight: $height-tabs_bar_extra_large; // 大尺寸页签文字行高
@@ -19,6 +19,9 @@
19
19
  height: 20px;
20
20
  padding: 2px 8px;
21
21
  }
22
+ .semi-tag-default:focus-visible, .semi-tag-small:focus-visible {
23
+ outline: 2px solid var(--semi-color-primary-light-active);
24
+ }
22
25
  .semi-tag-large {
23
26
  font-size: 12px;
24
27
  line-height: 16px;
@@ -26,6 +29,9 @@
26
29
  padding: 4px 8px;
27
30
  height: 24px;
28
31
  }
32
+ .semi-tag-large:focus-visible {
33
+ outline: 2px solid var(--semi-color-primary-light-active);
34
+ }
29
35
  .semi-tag-invisible {
30
36
  display: none;
31
37
  }
@@ -392,6 +398,10 @@
392
398
  color: var(--semi-color-text-0);
393
399
  }
394
400
 
401
+ .semi-tag-solid .semi-tag-close {
402
+ color: var(--semi-color-white);
403
+ }
404
+
395
405
  .semi-rtl .semi-tag,
396
406
  .semi-portal-rtl .semi-tag {
397
407
  direction: rtl;
@@ -25,12 +25,18 @@ $types: "ghost", "solid", "light";
25
25
  @include font-size-small;
26
26
  height: $height-tag_small;
27
27
  padding: $spacing-tag_small-paddingY $spacing-tag_small-paddingX;
28
+ &:focus-visible {
29
+ outline: $width-tag-outline solid $color-tag-outline-focus;
30
+ }
28
31
  }
29
32
 
30
33
  &-large {
31
34
  @include font-size-small;
32
35
  padding: $spacing-tag_large-paddingY $spacing-tag_large-paddingX;
33
36
  height: $height-tag_large;
37
+ &:focus-visible {
38
+ outline: $width-tag-outline solid $color-tag-outline-focus;
39
+ }
34
40
  }
35
41
 
36
42
  &-invisible {
@@ -46,12 +52,13 @@ $types: "ghost", "solid", "light";
46
52
  &-close {
47
53
  @include all-center;
48
54
  color: $color-tag_close-icon-default;
49
- padding-left: $spacing-tag_close-paddingLeft;
55
+ padding-left: $spacing-tag_close-paddingLeft;
50
56
  cursor: pointer;
51
57
  }
52
58
 
53
59
  &-closable {
54
- padding: $spacing-tag_closable-paddingTop $spacing-tag_closable-paddingRight $spacing-tag_closable-paddingBottom $spacing-tag_closable-paddingLeft;
60
+ padding: $spacing-tag_closable-paddingTop $spacing-tag_closable-paddingRight $spacing-tag_closable-paddingBottom
61
+ $spacing-tag_closable-paddingLeft;
55
62
  }
56
63
 
57
64
  &-avatar-square,
@@ -62,10 +69,10 @@ $types: "ghost", "solid", "light";
62
69
  }
63
70
 
64
71
  &-avatar-square {
65
- padding: $spacing-tag_avatar_square-paddingTop $spacing-tag_avatar_square-paddingRight $spacing-tag_avatar_square-paddingBottom $spacing-tag_avatar_square-paddingLeft;
72
+ padding: $spacing-tag_avatar_square-paddingTop $spacing-tag_avatar_square-paddingRight
73
+ $spacing-tag_avatar_square-paddingBottom $spacing-tag_avatar_square-paddingLeft;
66
74
 
67
75
  .#{$prefix}-avatar {
68
-
69
76
  & > img {
70
77
  background-color: $color-tag_avatar_square_img-bg-default;
71
78
  }
@@ -73,10 +80,12 @@ $types: "ghost", "solid", "light";
73
80
  }
74
81
 
75
82
  &-avatar-circle {
76
- padding: $spacing-tag_avatar_circle-paddingTop $spacing-tag_avatar_circle-paddingRight $spacing-tag_avatar_circle-paddingBottom $spacing-tag_avatar_circle-paddingLeft;
83
+ padding: $spacing-tag_avatar_circle-paddingTop $spacing-tag_avatar_circle-paddingRight
84
+ $spacing-tag_avatar_circle-paddingBottom $spacing-tag_avatar_circle-paddingLeft;
77
85
  }
78
86
 
79
- &-avatar-square.#{$module}-default, &-avatar-square.#{$module}-small {
87
+ &-avatar-square.#{$module}-default,
88
+ &-avatar-square.#{$module}-small {
80
89
  .#{$prefix}-avatar {
81
90
  width: $height-tag_small;
82
91
  height: $height-tag_small;
@@ -179,4 +188,10 @@ $types: "ghost", "solid", "light";
179
188
  color: $color-tag_avatar-text-default;
180
189
  }
181
190
 
182
- @import "./rtl.scss";
191
+ .#{$module}-solid {
192
+ .#{$module}-close {
193
+ color: $color-tag_close-icon_deep-default;
194
+ }
195
+ }
196
+
197
+ @import './rtl.scss';
@@ -7,12 +7,16 @@ $color-tag_white-border-default: rgba(var(--semi-grey-2), 0.7); // 白色标签
7
7
  $color-tag_white-text-default: var(--semi-color-text-0); // 白色标签文字颜色 - 默认
8
8
  $color-tag_white-icon-default: var(--semi-color-text-2); // 白色标签图标颜色 - 默认
9
9
 
10
+ $color-tag-outline-focus: var(--semi-color-primary-light-active); // 标签轮廓 - 聚焦
11
+
10
12
  $width-tag_avatar_circle_small: 16px; // 头像标签圆角 - 小尺寸
11
13
  $width-tag_avatar_circle_large: 20px; // 头像标签圆角 - 大尺寸
12
14
 
13
15
  $width-tag-border: 1px; // 标签描边宽度
14
16
  $width-tag_avatar-border: $width-tag-border; // 头像标签描边宽度
15
17
 
18
+ $width-tag-outline: 2px; // 标签轮廓宽度
19
+
16
20
  $height-tag_small: 20px; // 小尺寸标签高度
17
21
  $height-tag_large: 24px; // 大尺寸标签高度
18
22
  $radius-tag: var(--semi-border-radius-small); // 标签圆角大小
@@ -24,6 +28,7 @@ $spacing-tag_large-paddingY: 4px; // 大尺寸标签垂直方向内边距
24
28
  $spacing-tag_large-paddingX: $spacing-tight; // 大尺寸标签水平方向内边距
25
29
 
26
30
  $color-tag_close-icon-default: var(--semi-color-text-2); // 可删除的标签删除按钮颜色
31
+ $color-tag_close-icon_deep-default: var(--semi-color-white); // 深色模式下可删除的标签删除按钮颜色
27
32
  $spacing-tag_close-paddingLeft: $spacing-extra-tight; // 可删除的标签删除按钮左侧内边距
28
33
  $spacing-tag_closable-paddingTop: $spacing-extra-tight; // 可删除的标签删除按钮顶部内边距
29
34
  $spacing-tag_closable-paddingRight: $spacing-extra-tight; // 可删除的标签删除按钮右侧内边距
@@ -0,0 +1,25 @@
1
+ declare type FocusRedirectListener = (element: HTMLElement) => boolean;
2
+ interface HandleOptions {
3
+ enable?: boolean;
4
+ onFocusRedirectListener?: FocusRedirectListener | FocusRedirectListener[];
5
+ }
6
+ declare class FocusTrapHandle {
7
+ container: HTMLElement;
8
+ private options;
9
+ private focusRedirectListenerList;
10
+ private _enable;
11
+ constructor(container: HTMLElement, options?: HandleOptions);
12
+ addFocusRedirectListener: (listener: FocusRedirectListener) => () => void;
13
+ removeFocusRedirectListener: (listener: FocusRedirectListener) => void;
14
+ get enable(): boolean;
15
+ set enable(value: boolean);
16
+ destroy: () => void;
17
+ private shouldFocusRedirect;
18
+ private focusElement;
19
+ private onKeyPress;
20
+ private handleContainerTabKeyDown;
21
+ private handleContainerShiftTabKeyDown;
22
+ static getFocusableElements(node: HTMLElement): HTMLElement[];
23
+ static getActiveElement(): HTMLElement | null;
24
+ }
25
+ export default FocusTrapHandle;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+
3
+ var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
4
+
5
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
6
+
7
+ _Object$defineProperty(exports, "__esModule", {
8
+ value: true
9
+ });
10
+
11
+ exports.default = void 0;
12
+
13
+ var _freeze = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/freeze"));
14
+
15
+ var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
16
+
17
+ var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from"));
18
+
19
+ var _without2 = _interopRequireDefault(require("lodash-es/without"));
20
+
21
+ var _dom = require("@douyinfe/semi-foundation/utils/dom");
22
+
23
+ /*
24
+ * Usage:
25
+ * // Eg1: Pass a dom as the tab tarp container.
26
+ * const handle = new FocusTrapHandle(container, { enable: true });
27
+ *
28
+ * // Eg2: The focus redirect listener will be triggered when user pressed tab whiling last focusable dom is focusing in trap dom, return false to cancel redirect and use the browser normal tab focus index.
29
+ * handle.addFocusRedirectListener((e)=>{
30
+ * return true; // return false to prevent redirect on target DOM;
31
+ * });
32
+ *
33
+ * // Eg3: Set it to false in order to disable tab tarp at any moment;
34
+ * handle.enable = true;
35
+ *
36
+ * // Eg4: Destroy instance when component is unmounting for saving resource;
37
+ * handle.destroy();
38
+ *
39
+ * */
40
+ class FocusTrapHandle {
41
+ constructor(container, options) {
42
+ var _a;
43
+
44
+ this.addFocusRedirectListener = listener => {
45
+ this.focusRedirectListenerList.push(listener);
46
+ return () => this.removeFocusRedirectListener(listener);
47
+ };
48
+
49
+ this.removeFocusRedirectListener = listener => {
50
+ this.focusRedirectListenerList = (0, _without2.default)(this.focusRedirectListenerList, listener);
51
+ };
52
+
53
+ this.destroy = () => {
54
+ var _a;
55
+
56
+ (_a = this.container) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', this.onKeyPress);
57
+ }; // ---- private func ----
58
+
59
+
60
+ this.shouldFocusRedirect = element => {
61
+ if (!this.enable) {
62
+ return false;
63
+ }
64
+
65
+ for (const listener of this.focusRedirectListenerList) {
66
+ const should = listener(element);
67
+
68
+ if (!should) {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ return true;
74
+ };
75
+
76
+ this.focusElement = (element, event) => {
77
+ element === null || element === void 0 ? void 0 : element.focus();
78
+ event.preventDefault(); // prevent browser default tab move behavior
79
+ };
80
+
81
+ this.onKeyPress = event => {
82
+ if (event && event.key === 'Tab') {
83
+ const focusableElements = FocusTrapHandle.getFocusableElements(this.container);
84
+ const focusableNum = focusableElements.length;
85
+
86
+ if (focusableNum) {
87
+ // Shift + Tab will move focus backward
88
+ if (event.shiftKey) {
89
+ this.handleContainerShiftTabKeyDown(focusableElements, event);
90
+ } else {
91
+ this.handleContainerTabKeyDown(focusableElements, event);
92
+ }
93
+ }
94
+ }
95
+ };
96
+
97
+ this.handleContainerTabKeyDown = (focusableElements, event) => {
98
+ const activeElement = FocusTrapHandle.getActiveElement();
99
+ const isLastCurrentFocus = focusableElements[focusableElements.length - 1] === activeElement;
100
+ const redirectForcingElement = focusableElements[0];
101
+
102
+ if (isLastCurrentFocus && this.shouldFocusRedirect(redirectForcingElement)) {
103
+ this.focusElement(redirectForcingElement, event);
104
+ }
105
+ };
106
+
107
+ this.handleContainerShiftTabKeyDown = (focusableElements, event) => {
108
+ const activeElement = FocusTrapHandle.getActiveElement();
109
+ const isFirstCurrentFocus = focusableElements[0] === activeElement;
110
+ const redirectForcingElement = focusableElements[focusableElements.length - 1];
111
+
112
+ if (isFirstCurrentFocus && this.shouldFocusRedirect(redirectForcingElement)) {
113
+ this.focusElement(redirectForcingElement, event);
114
+ }
115
+ };
116
+
117
+ (0, _freeze.default)(options); // prevent user to change options after init;
118
+
119
+ this.container = container;
120
+ this.options = options;
121
+ this.enable = (_a = options === null || options === void 0 ? void 0 : options.enable) !== null && _a !== void 0 ? _a : true;
122
+
123
+ this.focusRedirectListenerList = (() => {
124
+ if (options === null || options === void 0 ? void 0 : options.onFocusRedirectListener) {
125
+ return (0, _isArray.default)(options.onFocusRedirectListener) ? [...options.onFocusRedirectListener] : [options.onFocusRedirectListener];
126
+ } else {
127
+ return [];
128
+ }
129
+ })();
130
+
131
+ this.container.addEventListener('keydown', this.onKeyPress);
132
+ }
133
+
134
+ get enable() {
135
+ return this._enable;
136
+ }
137
+
138
+ set enable(value) {
139
+ this._enable = value;
140
+ } // ---- static func ----
141
+
142
+
143
+ static getFocusableElements(node) {
144
+ if (!(0, _dom.isHTMLElement)(node)) {
145
+ return [];
146
+ }
147
+
148
+ const focusableSelectorsList = ["input:not([disabled]):not([tabindex='-1'])", "textarea:not([disabled]):not([tabindex='-1'])", "button:not([disabled]):not([tabindex='-1'])", "a[href]:not([tabindex='-1'])", "select:not([disabled]):not([tabindex='-1'])", "area[href]:not([tabindex='-1'])", "iframe:not([tabindex='-1'])", "object:not([tabindex='-1'])", "*[tabindex]:not([tabindex='-1'])", "*[contenteditable]:not([tabindex='-1'])"];
149
+ const focusableSelectorsStr = focusableSelectorsList.join(','); // we are not filtered elements which are invisible
150
+
151
+ return (0, _from.default)(node.querySelectorAll(focusableSelectorsStr));
152
+ }
153
+
154
+ static getActiveElement() {
155
+ return document ? document.activeElement : null;
156
+ }
157
+
158
+ }
159
+
160
+ var _default = FocusTrapHandle;
161
+ exports.default = _default;
@@ -0,0 +1 @@
1
+ export declare function handlePrevent(event: any): void;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
4
+
5
+ _Object$defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+
9
+ exports.handlePrevent = handlePrevent;
10
+
11
+ function handlePrevent(event) {
12
+ event.stopPropagation();
13
+ event.preventDefault();
14
+ }