no-frills-ui 0.0.14-alpha.3 → 0.0.14-alpha.5
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 +1622 -539
- package/dist/index.js.map +1 -1
- package/lib-esm/components/Accordion/AccordionStep.d.ts.map +1 -1
- package/lib-esm/components/Accordion/AccordionStep.js +27 -38
- package/lib-esm/components/Accordion/AccordionStep.js.map +1 -1
- package/lib-esm/components/Badge/Badge.js +7 -7
- package/lib-esm/components/Badge/Badge.js.map +1 -1
- package/lib-esm/components/Button/ActionButton.d.ts.map +1 -1
- package/lib-esm/components/Button/ActionButton.js +12 -11
- package/lib-esm/components/Button/ActionButton.js.map +1 -1
- package/lib-esm/components/Button/Button.d.ts +2 -2
- package/lib-esm/components/Button/Button.d.ts.map +1 -1
- package/lib-esm/components/Button/Button.js +19 -14
- package/lib-esm/components/Button/Button.js.map +1 -1
- package/lib-esm/components/Button/IconButton.d.ts.map +1 -1
- package/lib-esm/components/Button/IconButton.js +15 -14
- package/lib-esm/components/Button/IconButton.js.map +1 -1
- package/lib-esm/components/Button/LinkButton.d.ts.map +1 -1
- package/lib-esm/components/Button/LinkButton.js +7 -6
- package/lib-esm/components/Button/LinkButton.js.map +1 -1
- package/lib-esm/components/Button/RaisedButton.d.ts.map +1 -1
- package/lib-esm/components/Button/RaisedButton.js +15 -14
- package/lib-esm/components/Button/RaisedButton.js.map +1 -1
- package/lib-esm/components/Card/Card.d.ts.map +1 -1
- package/lib-esm/components/Card/Card.js +4 -4
- package/lib-esm/components/Card/Card.js.map +1 -1
- package/lib-esm/components/Chip/Chip.d.ts +5 -2
- package/lib-esm/components/Chip/Chip.d.ts.map +1 -1
- package/lib-esm/components/Chip/Chip.js +20 -14
- package/lib-esm/components/Chip/Chip.js.map +1 -1
- package/lib-esm/components/ChipInput/ChipInput.d.ts +9 -0
- package/lib-esm/components/ChipInput/ChipInput.d.ts.map +1 -1
- package/lib-esm/components/ChipInput/ChipInput.js +63 -24
- package/lib-esm/components/ChipInput/ChipInput.js.map +1 -1
- package/lib-esm/components/Dialog/AlertDialog.d.ts.map +1 -1
- package/lib-esm/components/Dialog/AlertDialog.js +4 -1
- package/lib-esm/components/Dialog/AlertDialog.js.map +1 -1
- package/lib-esm/components/Dialog/Dialog.d.ts +32 -2
- package/lib-esm/components/Dialog/Dialog.d.ts.map +1 -1
- package/lib-esm/components/Dialog/Dialog.js +84 -1
- package/lib-esm/components/Dialog/Dialog.js.map +1 -1
- package/lib-esm/components/DragAndDrop/DragAndDrop.d.ts +24 -0
- package/lib-esm/components/DragAndDrop/DragAndDrop.d.ts.map +1 -1
- package/lib-esm/components/DragAndDrop/DragAndDrop.js +85 -3
- package/lib-esm/components/DragAndDrop/DragAndDrop.js.map +1 -1
- package/lib-esm/components/DragAndDrop/DragItem.d.ts +4 -0
- package/lib-esm/components/DragAndDrop/DragItem.d.ts.map +1 -1
- package/lib-esm/components/DragAndDrop/DragItem.js +92 -12
- package/lib-esm/components/DragAndDrop/DragItem.js.map +1 -1
- package/lib-esm/components/DragAndDrop/types.d.ts +19 -0
- package/lib-esm/components/DragAndDrop/types.d.ts.map +1 -1
- package/lib-esm/components/DragAndDrop/types.js.map +1 -1
- package/lib-esm/components/Drawer/Drawer.d.ts +76 -1
- package/lib-esm/components/Drawer/Drawer.d.ts.map +1 -1
- package/lib-esm/components/Drawer/Drawer.js +158 -24
- package/lib-esm/components/Drawer/Drawer.js.map +1 -1
- package/lib-esm/components/Groups/Group.d.ts.map +1 -1
- package/lib-esm/components/Groups/Group.js +10 -8
- package/lib-esm/components/Groups/Group.js.map +1 -1
- package/lib-esm/components/Groups/GroupLabel.d.ts.map +1 -1
- package/lib-esm/components/Groups/GroupLabel.js +3 -3
- package/lib-esm/components/Groups/GroupLabel.js.map +1 -1
- package/lib-esm/components/Input/Checkbox.d.ts.map +1 -1
- package/lib-esm/components/Input/Checkbox.js +63 -58
- package/lib-esm/components/Input/Checkbox.js.map +1 -1
- package/lib-esm/components/Input/Dropdown.d.ts +8 -0
- package/lib-esm/components/Input/Dropdown.d.ts.map +1 -1
- package/lib-esm/components/Input/Dropdown.js +54 -31
- package/lib-esm/components/Input/Dropdown.js.map +1 -1
- package/lib-esm/components/Input/Input.d.ts.map +1 -1
- package/lib-esm/components/Input/Input.js +27 -22
- package/lib-esm/components/Input/Input.js.map +1 -1
- package/lib-esm/components/Input/Radio.d.ts.map +1 -1
- package/lib-esm/components/Input/Radio.js +58 -42
- package/lib-esm/components/Input/Radio.js.map +1 -1
- package/lib-esm/components/Input/RadioButton.d.ts.map +1 -1
- package/lib-esm/components/Input/RadioButton.js +12 -12
- package/lib-esm/components/Input/RadioButton.js.map +1 -1
- package/lib-esm/components/Input/Select.d.ts.map +1 -1
- package/lib-esm/components/Input/Select.js +27 -22
- package/lib-esm/components/Input/Select.js.map +1 -1
- package/lib-esm/components/Input/TextArea.d.ts.map +1 -1
- package/lib-esm/components/Input/TextArea.js +27 -22
- package/lib-esm/components/Input/TextArea.js.map +1 -1
- package/lib-esm/components/Input/Toggle.d.ts.map +1 -1
- package/lib-esm/components/Input/Toggle.js +17 -14
- package/lib-esm/components/Input/Toggle.js.map +1 -1
- package/lib-esm/components/Menu/Menu.d.ts +13 -1
- package/lib-esm/components/Menu/Menu.d.ts.map +1 -1
- package/lib-esm/components/Menu/Menu.js +98 -3
- package/lib-esm/components/Menu/Menu.js.map +1 -1
- package/lib-esm/components/Menu/MenuItem.d.ts +6 -3
- package/lib-esm/components/Menu/MenuItem.d.ts.map +1 -1
- package/lib-esm/components/Menu/MenuItem.js +10 -10
- package/lib-esm/components/Menu/MenuItem.js.map +1 -1
- package/lib-esm/components/Modal/Modal.d.ts +70 -1
- package/lib-esm/components/Modal/Modal.d.ts.map +1 -1
- package/lib-esm/components/Modal/Modal.js +145 -11
- package/lib-esm/components/Modal/Modal.js.map +1 -1
- package/lib-esm/components/Notification/Notification.d.ts +11 -7
- package/lib-esm/components/Notification/Notification.d.ts.map +1 -1
- package/lib-esm/components/Notification/Notification.js +54 -25
- package/lib-esm/components/Notification/Notification.js.map +1 -1
- package/lib-esm/components/Notification/NotificationManager.d.ts +11 -1
- package/lib-esm/components/Notification/NotificationManager.d.ts.map +1 -1
- package/lib-esm/components/Notification/NotificationManager.js +43 -8
- package/lib-esm/components/Notification/NotificationManager.js.map +1 -1
- package/lib-esm/components/Notification/style.d.ts +4 -0
- package/lib-esm/components/Notification/style.d.ts.map +1 -1
- package/lib-esm/components/Notification/style.js +30 -15
- package/lib-esm/components/Notification/style.js.map +1 -1
- package/lib-esm/components/Notification/types.d.ts +2 -0
- package/lib-esm/components/Notification/types.d.ts.map +1 -1
- package/lib-esm/components/Notification/types.js.map +1 -1
- package/lib-esm/components/Popover/Popover.d.ts.map +1 -1
- package/lib-esm/components/Popover/Popover.js +17 -2
- package/lib-esm/components/Popover/Popover.js.map +1 -1
- package/lib-esm/components/Spinner/Spinner.d.ts +3 -0
- package/lib-esm/components/Spinner/Spinner.d.ts.map +1 -1
- package/lib-esm/components/Spinner/Spinner.js +19 -4
- package/lib-esm/components/Spinner/Spinner.js.map +1 -1
- package/lib-esm/components/Stepper/Stepper.d.ts.map +1 -1
- package/lib-esm/components/Stepper/Stepper.js +29 -10
- package/lib-esm/components/Stepper/Stepper.js.map +1 -1
- package/lib-esm/components/Tabs/Tabs.d.ts.map +1 -1
- package/lib-esm/components/Tabs/Tabs.js +45 -12
- package/lib-esm/components/Tabs/Tabs.js.map +1 -1
- package/lib-esm/components/Toast/Toast.d.ts +25 -4
- package/lib-esm/components/Toast/Toast.d.ts.map +1 -1
- package/lib-esm/components/Toast/Toast.js +114 -18
- package/lib-esm/components/Toast/Toast.js.map +1 -1
- package/lib-esm/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/lib-esm/components/Tooltip/Tooltip.js +16 -5
- package/lib-esm/components/Tooltip/Tooltip.js.map +1 -1
- package/lib-esm/shared/LayerManager.d.ts.map +1 -1
- package/lib-esm/shared/LayerManager.js +63 -1
- package/lib-esm/shared/LayerManager.js.map +1 -1
- package/lib-esm/shared/constants.d.ts +58 -27
- package/lib-esm/shared/constants.d.ts.map +1 -1
- package/lib-esm/shared/constants.js +88 -25
- package/lib-esm/shared/constants.js.map +1 -1
- package/lib-esm/shared/styles.d.ts +1 -1
- package/lib-esm/shared/styles.d.ts.map +1 -1
- package/lib-esm/shared/styles.js +5 -3
- package/lib-esm/shared/styles.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,12 +36,94 @@ export { Header as DialogHeader, Body as DialogBody, Footer as DialogFooter, } f
|
|
|
36
36
|
class Dialog extends React.Component {
|
|
37
37
|
constructor() {
|
|
38
38
|
super(...arguments);
|
|
39
|
+
this.lastFocusedElement = null;
|
|
40
|
+
this.dialogRef = React.createRef();
|
|
39
41
|
this.state = {
|
|
40
42
|
show: false,
|
|
41
43
|
LayerComponent: undefined,
|
|
42
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Retrieves all focusable elements within the dialog.
|
|
47
|
+
*/
|
|
48
|
+
this.getFocusableElements = () => {
|
|
49
|
+
if (!this.dialogRef.current)
|
|
50
|
+
return [];
|
|
51
|
+
return Array.from(this.dialogRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'));
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Handles keydown events to implement the focus trap.
|
|
55
|
+
* Traps Tab and Shift+Tab within the dialog.
|
|
56
|
+
*/
|
|
57
|
+
this.handleKeyDown = (e) => {
|
|
58
|
+
if (e.key === 'Tab') {
|
|
59
|
+
const focusableElements = this.getFocusableElements();
|
|
60
|
+
if (focusableElements.length === 0)
|
|
61
|
+
return;
|
|
62
|
+
const firstElement = focusableElements[0];
|
|
63
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
64
|
+
if (e.shiftKey) {
|
|
65
|
+
if (document.activeElement === firstElement) {
|
|
66
|
+
lastElement.focus();
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (document.activeElement === lastElement) {
|
|
72
|
+
firstElement.focus();
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Restores focus to the element that was focused before the dialog opened.
|
|
80
|
+
*/
|
|
81
|
+
this.restoreFocus = () => {
|
|
82
|
+
if (this.lastFocusedElement) {
|
|
83
|
+
const elementToBeFocused = this.lastFocusedElement;
|
|
84
|
+
this.lastFocusedElement = null;
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
if (document.body.contains(elementToBeFocused)) {
|
|
87
|
+
elementToBeFocused.focus();
|
|
88
|
+
}
|
|
89
|
+
}, 100);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Callback ref to capture the Dialog DOM element.
|
|
94
|
+
* Triggers initial focus setting when the element mounts.
|
|
95
|
+
*/
|
|
96
|
+
this.setDialogRef = (node) => {
|
|
97
|
+
this.dialogRef.current = node;
|
|
98
|
+
if (node) {
|
|
99
|
+
this.setInitialFocus(node);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Sets initial focus within the dialog.
|
|
104
|
+
* Tries to focus the header first, then the first interactive element, or falls back to the container.
|
|
105
|
+
*/
|
|
106
|
+
this.setInitialFocus = (root) => {
|
|
107
|
+
const firstChild = root.firstElementChild;
|
|
108
|
+
if (firstChild) {
|
|
109
|
+
if (firstChild.getAttribute('tabindex') === null) {
|
|
110
|
+
firstChild.setAttribute('tabindex', '-1');
|
|
111
|
+
}
|
|
112
|
+
firstChild.focus();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const focusableElements = this.getFocusableElements();
|
|
116
|
+
if (focusableElements.length > 0) {
|
|
117
|
+
focusableElements[0].focus();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
root.focus();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
43
123
|
this.open = (closeCallback) => {
|
|
44
124
|
const _a = this.props, { closeOnEsc, closeOnOverlayClick, children } = _a, rest = __rest(_a, ["closeOnEsc", "closeOnOverlayClick", "children"]);
|
|
125
|
+
// Save current focus
|
|
126
|
+
this.lastFocusedElement = document.activeElement;
|
|
45
127
|
const [Component, closeFn] = LayerManager.renderLayer({
|
|
46
128
|
exitDelay: 300,
|
|
47
129
|
overlay: true,
|
|
@@ -49,7 +131,7 @@ class Dialog extends React.Component {
|
|
|
49
131
|
closeCallback: this.closeCallback,
|
|
50
132
|
closeOnOverlayClick,
|
|
51
133
|
position: LAYER_POSITION.DIALOG,
|
|
52
|
-
component: (_jsx(DialogContainer, Object.assign({}, rest, { onClick: (e) => e.stopPropagation(), elevated: true, children: children }))),
|
|
134
|
+
component: (_jsx(DialogContainer, Object.assign({}, rest, { ref: this.setDialogRef, role: "dialog", "aria-modal": "true", tabIndex: -1, onKeyDown: this.handleKeyDown, onClick: (e) => e.stopPropagation(), elevated: true, children: children }))),
|
|
53
135
|
});
|
|
54
136
|
this.closeDialog = closeFn;
|
|
55
137
|
this.setState({
|
|
@@ -64,6 +146,7 @@ class Dialog extends React.Component {
|
|
|
64
146
|
};
|
|
65
147
|
this.closeCallback = (resp) => {
|
|
66
148
|
var _a;
|
|
149
|
+
this.restoreFocus();
|
|
67
150
|
this.setState({
|
|
68
151
|
show: false,
|
|
69
152
|
LayerComponent: undefined,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,YAAY,EAAE,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;;;;;;;;;;;;;;;;CAgB1C,CAAC;AAEF,OAAO,EACH,MAAM,IAAI,YAAY,EACtB,IAAI,IAAI,UAAU,EAClB,MAAM,IAAI,YAAY,GACzB,MAAM,qBAAqB,CAAC;AAS7B,MAAM,MAAO,SAAQ,KAAK,CAAC,SAA8D;IAAzF;;QAgBI,UAAK,GAAgB;YACjB,IAAI,EAAE,KAAK;YACX,cAAc,EAAE,SAAS;SAC5B,CAAC;QAMK,SAAI,GAAG,CAAC,aAAuC,EAAE,EAAE;YACtD,MAAM,KAAyD,IAAI,CAAC,KAAK,EAAnE,EAAE,UAAU,EAAE,mBAAmB,EAAE,QAAQ,OAAwB,EAAnB,IAAI,cAApD,iDAAsD,CAAa,CAAC;YAE1E,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC;gBAClD,SAAS,EAAE,GAAG;gBACd,OAAO,EAAE,IAAI;gBACb,UAAU;gBACV,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,mBAAmB;gBACnB,QAAQ,EAAE,cAAc,CAAC,MAAM;gBAC/B,SAAS,EAAE,CACP,KAAC,eAAe,oBAAK,IAAI,IAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,EAAE,QAAQ,kBACnE,QAAQ,IACK,CACrB;aACJ,CAAC,CAAC;YAEH,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;YAE3B,IAAI,CAAC,QAAQ,CAAC;gBACV,IAAI,EAAE,IAAI;gBACV,cAAc,EAAE,SAAS;aAC5B,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;QACnC,CAAC,CAAC;QAEK,UAAK,GAAG,CAAC,IAAc,EAAE,EAAE;;YAC9B,MAAA,IAAI,CAAC,WAAW,qDAAG,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC;QAEM,kBAAa,GAAG,CAAC,IAAc,EAAE,EAAE;;YACvC,IAAI,CAAC,QAAQ,CAAC;gBACV,IAAI,EAAE,KAAK;gBACX,cAAc,EAAE,SAAS;aAC5B,CAAC,CAAC;YACH,MAAA,IAAI,CAAC,SAAS,qDAAG,IAAI,CAAC,CAAC;QAC3B,CAAC,CAAC;IAWN,CAAC;IAnDG,qBAAqB,CAAC,SAAwB,EAAE,SAAsB;QAClE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC;IAC9C,CAAC;IAwCD,MAAM;QACF,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAEtC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;YACpC,OAAO,KAAC,cAAc,KAAG,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;;AAtEM,gBAAS,GAAG;IACf,kEAAkE;IAClE,UAAU,EAAE,SAAS,CAAC,IAAI;IAC1B,6DAA6D;IAC7D,mBAAmB,EAAE,SAAS,CAAC,IAAI;CACtC,AALe,CAKd;AAEK,mBAAY,GAAG;IAClB,UAAU,EAAE,IAAI;IAChB,mBAAmB,EAAE,IAAI;CAC5B,AAHkB,CAGjB;AA+DN,eAAe,MAAM,CAAC","sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from '@emotion/styled';\nimport LayerManager, { LAYER_POSITION } from '../../shared/LayerManager';\nimport { Card } from '../Card';\n\nexport const DialogContainer = styled(Card)`\n max-width: 768px;\n max-height: 80vh;\n transform: scale(0);\n opacity: 0;\n transition: all 0.3s ease;\n\n .nf-layer-enter & {\n opacity: 1;\n transform: scale(1);\n }\n\n .nf-layer-exit & {\n opacity: 0;\n transform: scale(0);\n }\n`;\n\nexport {\n Header as DialogHeader,\n Body as DialogBody,\n Footer as DialogFooter,\n} from '../../shared/styles';\n\ntype DialogOptions = PropTypes.InferProps<typeof Dialog.propTypes>;\n\ninterface DialogState {\n show: boolean;\n LayerComponent?: () => React.ReactPortal | null;\n}\n\nclass Dialog extends React.Component<React.PropsWithChildren<DialogOptions>, DialogState> {\n static propTypes = {\n /** Flag to close dialog on `esc` click. Default value is true. */\n closeOnEsc: PropTypes.bool,\n /** Close layer overlay is clicked. Default value is true. */\n closeOnOverlayClick: PropTypes.bool,\n };\n\n static defaultProps = {\n closeOnEsc: true,\n closeOnOverlayClick: true,\n };\n\n private closeDialog: (resp?: unknown) => void;\n private onCloseFn: (resp?: unknown) => void;\n\n state: DialogState = {\n show: false,\n LayerComponent: undefined,\n };\n\n shouldComponentUpdate(nextProps: DialogOptions, nextState: DialogState) {\n return this.state.show !== nextState.show;\n }\n\n public open = (closeCallback?: (resp: unknown) => void) => {\n const { closeOnEsc, closeOnOverlayClick, children, ...rest } = this.props;\n\n const [Component, closeFn] = LayerManager.renderLayer({\n exitDelay: 300,\n overlay: true,\n closeOnEsc,\n closeCallback: this.closeCallback,\n closeOnOverlayClick,\n position: LAYER_POSITION.DIALOG,\n component: (\n <DialogContainer {...rest} onClick={(e) => e.stopPropagation()} elevated>\n {children}\n </DialogContainer>\n ),\n });\n\n this.closeDialog = closeFn;\n\n this.setState({\n show: true,\n LayerComponent: Component,\n });\n this.onCloseFn = closeCallback;\n };\n\n public close = (resp?: unknown) => {\n this.closeDialog?.(resp);\n };\n\n private closeCallback = (resp?: unknown) => {\n this.setState({\n show: false,\n LayerComponent: undefined,\n });\n this.onCloseFn?.(resp);\n };\n\n render() {\n const { LayerComponent } = this.state;\n\n if (this.state.show && LayerComponent) {\n return <LayerComponent />;\n } else {\n return null;\n }\n }\n}\n\nexport default Dialog;\n"]}
|
|
1
|
+
{"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from '@emotion/styled';\nimport LayerManager, { LAYER_POSITION } from '../../shared/LayerManager';\nimport { Card } from '../Card';\n\nexport const DialogContainer = styled(Card)`\n max-width: 768px;\n max-height: 80vh;\n transform: scale(0);\n opacity: 0;\n transition: all 0.3s ease;\n\n .nf-layer-enter & {\n opacity: 1;\n transform: scale(1);\n }\n\n .nf-layer-exit & {\n opacity: 0;\n transform: scale(0);\n }\n`;\n\nexport {\n Header as DialogHeader,\n Body as DialogBody,\n Footer as DialogFooter,\n} from '../../shared/styles';\n\ninterface DialogOptions {\n /** Flag to close dialog on `esc` click. Default value is true. */\n closeOnEsc?: boolean;\n /** Close layer overlay is clicked. Default value is true. */\n closeOnOverlayClick?: boolean;\n}\n\ninterface DialogState {\n show: boolean;\n LayerComponent?: () => React.ReactPortal | null;\n}\n\nclass Dialog extends React.Component<\n React.PropsWithChildren<DialogOptions> & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'ref'>,\n DialogState\n> {\n static propTypes = {\n /** Flag to close dialog on `esc` click. Default value is true. */\n closeOnEsc: PropTypes.bool,\n /** Close layer overlay is clicked. Default value is true. */\n closeOnOverlayClick: PropTypes.bool,\n };\n\n static defaultProps = {\n closeOnEsc: true,\n closeOnOverlayClick: true,\n };\n\n private closeDialog: (resp?: unknown) => void;\n private onCloseFn: (resp?: unknown) => void;\n private lastFocusedElement: HTMLElement | null = null;\n private dialogRef = React.createRef<HTMLDivElement>();\n\n state: DialogState = {\n show: false,\n LayerComponent: undefined,\n };\n\n /**\n * Retrieves all focusable elements within the dialog.\n */\n private getFocusableElements = (): HTMLElement[] => {\n if (!this.dialogRef.current) return [];\n return Array.from(\n this.dialogRef.current.querySelectorAll(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n ),\n ) as HTMLElement[];\n };\n\n /**\n * Handles keydown events to implement the focus trap.\n * Traps Tab and Shift+Tab within the dialog.\n */\n private handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Tab') {\n const focusableElements = this.getFocusableElements();\n if (focusableElements.length === 0) return;\n\n const firstElement = focusableElements[0];\n const lastElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === firstElement) {\n lastElement.focus();\n e.preventDefault();\n }\n } else {\n if (document.activeElement === lastElement) {\n firstElement.focus();\n e.preventDefault();\n }\n }\n }\n };\n\n /**\n * Restores focus to the element that was focused before the dialog opened.\n */\n private restoreFocus = () => {\n if (this.lastFocusedElement) {\n const elementToBeFocused = this.lastFocusedElement;\n this.lastFocusedElement = null;\n setTimeout(() => {\n if (document.body.contains(elementToBeFocused)) {\n elementToBeFocused.focus();\n }\n }, 100);\n }\n };\n\n /**\n * Callback ref to capture the Dialog DOM element.\n * Triggers initial focus setting when the element mounts.\n */\n private setDialogRef = (node: HTMLDivElement | null) => {\n (this.dialogRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n\n if (node) {\n this.setInitialFocus(node);\n }\n };\n\n /**\n * Sets initial focus within the dialog.\n * Tries to focus the header first, then the first interactive element, or falls back to the container.\n */\n private setInitialFocus = (root: HTMLElement) => {\n const firstChild = root.firstElementChild as HTMLElement;\n if (firstChild) {\n if (firstChild.getAttribute('tabindex') === null) {\n firstChild.setAttribute('tabindex', '-1');\n }\n firstChild.focus();\n return;\n }\n\n const focusableElements = this.getFocusableElements();\n if (focusableElements.length > 0) {\n focusableElements[0].focus();\n } else {\n root.focus();\n }\n };\n\n shouldComponentUpdate(nextProps: DialogOptions, nextState: DialogState) {\n return this.state.show !== nextState.show;\n }\n\n public open = (closeCallback?: (resp: unknown) => void) => {\n const { closeOnEsc, closeOnOverlayClick, children, ...rest } = this.props;\n\n // Save current focus\n this.lastFocusedElement = document.activeElement as HTMLElement;\n\n const [Component, closeFn] = LayerManager.renderLayer({\n exitDelay: 300,\n overlay: true,\n closeOnEsc,\n closeCallback: this.closeCallback,\n closeOnOverlayClick,\n position: LAYER_POSITION.DIALOG,\n component: (\n <DialogContainer\n {...rest}\n ref={this.setDialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n tabIndex={-1}\n onKeyDown={this.handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n elevated\n >\n {children}\n </DialogContainer>\n ),\n });\n\n this.closeDialog = closeFn;\n\n this.setState({\n show: true,\n LayerComponent: Component,\n });\n this.onCloseFn = closeCallback;\n };\n\n public close = (resp?: unknown) => {\n this.closeDialog?.(resp);\n };\n\n private closeCallback = (resp?: unknown) => {\n this.restoreFocus();\n this.setState({\n show: false,\n LayerComponent: undefined,\n });\n this.onCloseFn?.(resp);\n };\n\n render() {\n const { LayerComponent } = this.state;\n\n if (this.state.show && LayerComponent) {\n return <LayerComponent />;\n } else {\n return null;\n }\n }\n}\n\nexport default Dialog;\n"]}
|
|
@@ -7,6 +7,18 @@ type DragAndDropProps = {
|
|
|
7
7
|
onDrop: (start: number, end: number) => void;
|
|
8
8
|
/** Shows drag indicator against each list item */
|
|
9
9
|
showIndicator: boolean;
|
|
10
|
+
/** i18n: Template for item aria-label. Placeholders: {:position}, {:grabKey}, {:moveKeys}, {:dropKey}, {:altDropKey} */
|
|
11
|
+
itemAriaLabelTemplate?: string;
|
|
12
|
+
/** i18n: Aria label for drag handle */
|
|
13
|
+
dragHandleAriaLabel?: string;
|
|
14
|
+
/** i18n: Template for grabbed announcement. Placeholders: {:position}, {:moveKeys}, {:dropKey}, {:altDropKey}, {:cancelKey} */
|
|
15
|
+
grabbedAnnouncementTemplate?: string;
|
|
16
|
+
/** i18n: Template for moved announcement. Placeholders: {:position} */
|
|
17
|
+
movedAnnouncementTemplate?: string;
|
|
18
|
+
/** i18n: Template for dropped announcement. Placeholders: {:position} */
|
|
19
|
+
droppedAnnouncementTemplate?: string;
|
|
20
|
+
/** i18n: Template for cancelled announcement */
|
|
21
|
+
cancelledAnnouncementTemplate?: string;
|
|
10
22
|
} & PropsWithChildren<unknown>;
|
|
11
23
|
/**
|
|
12
24
|
* A drag and drop container component that enables reordering of child elements.
|
|
@@ -40,6 +52,18 @@ declare namespace DragAndDrop {
|
|
|
40
52
|
orientation: ORIENTATION;
|
|
41
53
|
/** Whether to display drag indicators for each list item */
|
|
42
54
|
showIndicator: boolean;
|
|
55
|
+
/** Default item aria-label template */
|
|
56
|
+
itemAriaLabelTemplate: string;
|
|
57
|
+
/** Default drag handle aria-label */
|
|
58
|
+
dragHandleAriaLabel: string;
|
|
59
|
+
/** Default grabbed announcement template */
|
|
60
|
+
grabbedAnnouncementTemplate: string;
|
|
61
|
+
/** Default moved announcement template */
|
|
62
|
+
movedAnnouncementTemplate: string;
|
|
63
|
+
/** Default dropped announcement template */
|
|
64
|
+
droppedAnnouncementTemplate: string;
|
|
65
|
+
/** Default cancelled announcement template */
|
|
66
|
+
cancelledAnnouncementTemplate: string;
|
|
43
67
|
};
|
|
44
68
|
}
|
|
45
69
|
export default DragAndDrop;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DragAndDrop.d.ts","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragAndDrop.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,iBAAiB,EAAY,MAAM,OAAO,CAAC;AAG3D,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAEnD,KAAK,gBAAgB,GAAG;IACpB,qCAAqC;IACrC,WAAW,EAAE,WAAW,CAAC;IACzB,yBAAyB;IACzB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,kDAAkD;IAClD,aAAa,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DragAndDrop.d.ts","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragAndDrop.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,iBAAiB,EAAY,MAAM,OAAO,CAAC;AAG3D,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAEnD,KAAK,gBAAgB,GAAG;IACpB,qCAAqC;IACrC,WAAW,EAAE,WAAW,CAAC;IACzB,yBAAyB;IACzB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,kDAAkD;IAClD,aAAa,EAAE,OAAO,CAAC;IACvB,wHAAwH;IACxH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uCAAuC;IACvC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+HAA+H;IAC/H,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,uEAAuE;IACvE,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,yEAAyE;IACzE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,gDAAgD;IAChD,6BAA6B,CAAC,EAAE,MAAM,CAAC;CAC1C,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAwB/B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAwB,WAAW,CAAC,KAAK,EAAE,gBAAgB,oDAiI1D;kBAjIuB,WAAW;;QAoI/B,qCAAqC;;QAErC,4DAA4D;;QAE5D,uCAAuC;;QAGvC,qCAAqC;;QAErC,4CAA4C;;QAG5C,0CAA0C;;QAE1C,4CAA4C;;QAE5C,8CAA8C;;;;eApJ1B,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
3
|
import styled from '@emotion/styled';
|
|
4
4
|
import DragItem from './DragItem';
|
|
@@ -11,6 +11,18 @@ const Container = styled.div `
|
|
|
11
11
|
flex-wrap: wrap;
|
|
12
12
|
flex-direction: ${(props) => (props.orientation === ORIENTATION.HORIZONTAL ? 'row' : 'column')};
|
|
13
13
|
`;
|
|
14
|
+
/** Visually hidden but accessible to screen readers */
|
|
15
|
+
const VisuallyHidden = styled.div `
|
|
16
|
+
position: absolute;
|
|
17
|
+
width: 1px;
|
|
18
|
+
height: 1px;
|
|
19
|
+
padding: 0;
|
|
20
|
+
margin: -1px;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
clip: rect(0, 0, 0, 0);
|
|
23
|
+
white-space: nowrap;
|
|
24
|
+
border-width: 0;
|
|
25
|
+
`;
|
|
14
26
|
/**
|
|
15
27
|
* A drag and drop container component that enables reordering of child elements.
|
|
16
28
|
*
|
|
@@ -37,10 +49,37 @@ const Container = styled.div `
|
|
|
37
49
|
* @returns {JSX.Element} A draggable container with reorderable items
|
|
38
50
|
*/
|
|
39
51
|
export default function DragAndDrop(props) {
|
|
40
|
-
const { orientation, children, onDrop, showIndicator } = props;
|
|
52
|
+
const { orientation, children, onDrop, showIndicator, itemAriaLabelTemplate, dragHandleAriaLabel, grabbedAnnouncementTemplate, movedAnnouncementTemplate, droppedAnnouncementTemplate, cancelledAnnouncementTemplate, } = props;
|
|
41
53
|
const [startIndex, setStartIndex] = useState(null);
|
|
54
|
+
const [originalIndex, setOriginalIndex] = useState(null);
|
|
42
55
|
const [isDragging, setIsDragging] = useState(false);
|
|
43
56
|
const [dragOver, setDragOver] = useState(null);
|
|
57
|
+
const [announcement, setAnnouncement] = useState('');
|
|
58
|
+
const childrenArray = React.Children.toArray(children);
|
|
59
|
+
const totalItems = childrenArray.length;
|
|
60
|
+
/**
|
|
61
|
+
* Replace placeholders in i18n templates
|
|
62
|
+
*/
|
|
63
|
+
const replacePlaceholders = (template, data) => {
|
|
64
|
+
var _a, _b, _c, _d, _e, _f;
|
|
65
|
+
return template
|
|
66
|
+
.replace(/\{:position\}/g, String((_a = data.position) !== null && _a !== void 0 ? _a : ''))
|
|
67
|
+
.replace(/\{:grabKey\}/g, (_b = data.grabKey) !== null && _b !== void 0 ? _b : 'Space')
|
|
68
|
+
.replace(/\{:dropKey\}/g, (_c = data.dropKey) !== null && _c !== void 0 ? _c : 'Space')
|
|
69
|
+
.replace(/\{:altDropKey\}/g, (_d = data.altDropKey) !== null && _d !== void 0 ? _d : 'Enter')
|
|
70
|
+
.replace(/\{:cancelKey\}/g, (_e = data.cancelKey) !== null && _e !== void 0 ? _e : 'Escape')
|
|
71
|
+
.replace(/\{:moveKeys\}/g, (_f = data.moveKeys) !== null && _f !== void 0 ? _f : (orientation === ORIENTATION.VERTICAL ? 'Arrow Up/Down' : 'Arrow Left/Right'));
|
|
72
|
+
};
|
|
73
|
+
// i18n configuration object
|
|
74
|
+
const i18n = {
|
|
75
|
+
itemAriaLabelTemplate: itemAriaLabelTemplate,
|
|
76
|
+
dragHandleAriaLabel: dragHandleAriaLabel,
|
|
77
|
+
grabbedAnnouncementTemplate: grabbedAnnouncementTemplate,
|
|
78
|
+
movedAnnouncementTemplate: movedAnnouncementTemplate,
|
|
79
|
+
droppedAnnouncementTemplate: droppedAnnouncementTemplate,
|
|
80
|
+
cancelledAnnouncementTemplate: cancelledAnnouncementTemplate,
|
|
81
|
+
replacePlaceholders,
|
|
82
|
+
};
|
|
44
83
|
/**
|
|
45
84
|
* Drop handler invoked when a draggable item is released.
|
|
46
85
|
* @param index
|
|
@@ -50,14 +89,57 @@ export default function DragAndDrop(props) {
|
|
|
50
89
|
onDrop === null || onDrop === void 0 ? void 0 : onDrop(startIndex, index);
|
|
51
90
|
}
|
|
52
91
|
setStartIndex(null);
|
|
92
|
+
setOriginalIndex(null);
|
|
93
|
+
setIsDragging(false);
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Cancel handler to restore item to original position
|
|
97
|
+
*/
|
|
98
|
+
const cancel = () => {
|
|
99
|
+
if (originalIndex !== null && startIndex !== null && startIndex !== originalIndex) {
|
|
100
|
+
onDrop === null || onDrop === void 0 ? void 0 : onDrop(startIndex, originalIndex);
|
|
101
|
+
}
|
|
102
|
+
setStartIndex(null);
|
|
103
|
+
setOriginalIndex(null);
|
|
53
104
|
setIsDragging(false);
|
|
54
105
|
};
|
|
55
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Start grab handler to track original position
|
|
108
|
+
*/
|
|
109
|
+
const startGrab = (index) => {
|
|
110
|
+
setStartIndex(index);
|
|
111
|
+
setOriginalIndex(index);
|
|
112
|
+
setIsDragging(true);
|
|
113
|
+
};
|
|
114
|
+
return (_jsxs(_Fragment, { children: [_jsx(DragContext.Provider, { value: {
|
|
115
|
+
startIndex,
|
|
116
|
+
setStartIndex,
|
|
117
|
+
drop,
|
|
118
|
+
onDrop,
|
|
119
|
+
cancel,
|
|
120
|
+
startGrab,
|
|
121
|
+
isDragging,
|
|
122
|
+
setIsDragging,
|
|
123
|
+
setDragOver,
|
|
124
|
+
i18n,
|
|
125
|
+
}, children: _jsx(Container, { orientation: orientation, role: "list", children: React.Children.map(childrenArray, (child, index) => (_jsx(DragItem, { index: index, orientation: orientation, showIndicator: showIndicator, dragOver: dragOver, totalItems: totalItems, setAnnouncement: setAnnouncement, children: child }))) }) }), _jsx(VisuallyHidden, { role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement })] }));
|
|
56
126
|
}
|
|
57
127
|
DragAndDrop.defaultProps = {
|
|
58
128
|
/** Orientation of the list layout */
|
|
59
129
|
orientation: ORIENTATION.VERTICAL,
|
|
60
130
|
/** Whether to display drag indicators for each list item */
|
|
61
131
|
showIndicator: false,
|
|
132
|
+
/** Default item aria-label template */
|
|
133
|
+
itemAriaLabelTemplate: 'Item {:position}. Press {:grabKey} to grab, {:moveKeys} to move, {:dropKey} or {:altDropKey} to drop',
|
|
134
|
+
/** Default drag handle aria-label */
|
|
135
|
+
dragHandleAriaLabel: 'Drag to reorder',
|
|
136
|
+
/** Default grabbed announcement template */
|
|
137
|
+
grabbedAnnouncementTemplate: 'Item {:position} grabbed. Use {:moveKeys} to move, {:dropKey} or {:altDropKey} to drop, {:cancelKey} to cancel',
|
|
138
|
+
/** Default moved announcement template */
|
|
139
|
+
movedAnnouncementTemplate: 'Item moved to position {:position}',
|
|
140
|
+
/** Default dropped announcement template */
|
|
141
|
+
droppedAnnouncementTemplate: 'Item dropped at position {:position}',
|
|
142
|
+
/** Default cancelled announcement template */
|
|
143
|
+
cancelledAnnouncementTemplate: 'Drag cancelled, item restored to original position',
|
|
62
144
|
};
|
|
63
145
|
//# sourceMappingURL=DragAndDrop.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DragAndDrop.js","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragAndDrop.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAqB,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,QAAQ,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAWnD,0BAA0B;AAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAA8B;;;;;sBAKpC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,KAAK,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;CACjG,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,KAAuB;IACvD,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAC/D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAS,IAAI,CAAC,CAAC;IAC3D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC7D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAS,IAAI,CAAC,CAAC;IAEvD;;;OAGG;IACH,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE;QAC3B,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,UAAU,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,aAAa,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,CAAC;IAEF,OAAO,CACH,KAAC,WAAW,CAAC,QAAQ,IACjB,KAAK,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,YAElF,KAAC,SAAS,IAAC,WAAW,EAAE,WAAW,YAC9B,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAC5C,KAAC,QAAQ,IACL,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,QAAQ,YAEjB,KAAK,GACC,CACd,CAAC,GACM,GACO,CAC1B,CAAC;AACN,CAAC;AAED,WAAW,CAAC,YAAY,GAAG;IACvB,qCAAqC;IACrC,WAAW,EAAE,WAAW,CAAC,QAAQ;IACjC,4DAA4D;IAC5D,aAAa,EAAE,KAAK;CACvB,CAAC","sourcesContent":["import React, { PropsWithChildren, useState } from 'react';\nimport styled from '@emotion/styled';\nimport DragItem from './DragItem';\nimport { ORIENTATION, DragContext } from './types';\n\ntype DragAndDropProps = {\n /** Orientation of the list layout */\n orientation: ORIENTATION;\n /** Drop event handler */\n onDrop: (start: number, end: number) => void;\n /** Shows drag indicator against each list item */\n showIndicator: boolean;\n} & PropsWithChildren<unknown>;\n\n/** Container Component */\nconst Container = styled.div<{ orientation: ORIENTATION }>`\n flex: 1;\n display: flex;\n position: relative;\n flex-wrap: wrap;\n flex-direction: ${(props) => (props.orientation === ORIENTATION.HORIZONTAL ? 'row' : 'column')};\n`;\n\n/**\n * A drag and drop container component that enables reordering of child elements.\n *\n * @component\n * @example\n * ```tsx\n * <DragAndDrop\n * orientation={ORIENTATION.VERTICAL}\n * onDrop={(start, end) => handleReorder(start, end)}\n * showIndicator={true}\n * >\n * <div>Item 1</div>\n * <div>Item 2</div>\n * <div>Item 3</div>\n * </DragAndDrop>\n * ```\n *\n * @param {DragAndDropProps} props - The component props\n * @param {ORIENTATION} props.orientation - Determines the layout direction (horizontal or vertical). Defaults to VERTICAL.\n * @param {(start: number, end: number) => void} props.onDrop - Callback fired when an item is dropped, receives the start and end indices\n * @param {boolean} props.showIndicator - Whether to display drag indicators for each list item. Defaults to false.\n * @param {React.ReactNode} props.children - Child elements to be rendered as draggable items\n *\n * @returns {JSX.Element} A draggable container with reorderable items\n */\nexport default function DragAndDrop(props: DragAndDropProps) {\n const { orientation, children, onDrop, showIndicator } = props;\n const [startIndex, setStartIndex] = useState<number>(null);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [dragOver, setDragOver] = useState<number>(null);\n\n /**\n * Drop handler invoked when a draggable item is released.\n * @param index\n */\n const drop = (index: number) => {\n if (startIndex !== null) {\n onDrop?.(startIndex, index);\n }\n setStartIndex(null);\n setIsDragging(false);\n };\n\n return (\n <DragContext.Provider\n value={{ startIndex, setStartIndex, drop, isDragging, setIsDragging, setDragOver }}\n >\n <Container orientation={orientation}>\n {React.Children.map(children, (child, index) => (\n <DragItem\n index={index}\n orientation={orientation}\n showIndicator={showIndicator}\n dragOver={dragOver}\n >\n {child}\n </DragItem>\n ))}\n </Container>\n </DragContext.Provider>\n );\n}\n\nDragAndDrop.defaultProps = {\n /** Orientation of the list layout */\n orientation: ORIENTATION.VERTICAL,\n /** Whether to display drag indicators for each list item */\n showIndicator: false,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"DragAndDrop.js","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragAndDrop.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { PropsWithChildren, useState } from 'react';\nimport styled from '@emotion/styled';\nimport DragItem from './DragItem';\nimport { ORIENTATION, DragContext } from './types';\n\ntype DragAndDropProps = {\n /** Orientation of the list layout */\n orientation: ORIENTATION;\n /** Drop event handler */\n onDrop: (start: number, end: number) => void;\n /** Shows drag indicator against each list item */\n showIndicator: boolean;\n /** i18n: Template for item aria-label. Placeholders: {:position}, {:grabKey}, {:moveKeys}, {:dropKey}, {:altDropKey} */\n itemAriaLabelTemplate?: string;\n /** i18n: Aria label for drag handle */\n dragHandleAriaLabel?: string;\n /** i18n: Template for grabbed announcement. Placeholders: {:position}, {:moveKeys}, {:dropKey}, {:altDropKey}, {:cancelKey} */\n grabbedAnnouncementTemplate?: string;\n /** i18n: Template for moved announcement. Placeholders: {:position} */\n movedAnnouncementTemplate?: string;\n /** i18n: Template for dropped announcement. Placeholders: {:position} */\n droppedAnnouncementTemplate?: string;\n /** i18n: Template for cancelled announcement */\n cancelledAnnouncementTemplate?: string;\n} & PropsWithChildren<unknown>;\n\n/** Container Component */\nconst Container = styled.div<{ orientation: ORIENTATION }>`\n flex: 1;\n display: flex;\n position: relative;\n flex-wrap: wrap;\n flex-direction: ${(props) => (props.orientation === ORIENTATION.HORIZONTAL ? 'row' : 'column')};\n`;\n\n/** Visually hidden but accessible to screen readers */\nconst VisuallyHidden = styled.div`\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n`;\n\n/**\n * A drag and drop container component that enables reordering of child elements.\n *\n * @component\n * @example\n * ```tsx\n * <DragAndDrop\n * orientation={ORIENTATION.VERTICAL}\n * onDrop={(start, end) => handleReorder(start, end)}\n * showIndicator={true}\n * >\n * <div>Item 1</div>\n * <div>Item 2</div>\n * <div>Item 3</div>\n * </DragAndDrop>\n * ```\n *\n * @param {DragAndDropProps} props - The component props\n * @param {ORIENTATION} props.orientation - Determines the layout direction (horizontal or vertical). Defaults to VERTICAL.\n * @param {(start: number, end: number) => void} props.onDrop - Callback fired when an item is dropped, receives the start and end indices\n * @param {boolean} props.showIndicator - Whether to display drag indicators for each list item. Defaults to false.\n * @param {React.ReactNode} props.children - Child elements to be rendered as draggable items\n *\n * @returns {JSX.Element} A draggable container with reorderable items\n */\nexport default function DragAndDrop(props: DragAndDropProps) {\n const {\n orientation,\n children,\n onDrop,\n showIndicator,\n itemAriaLabelTemplate,\n dragHandleAriaLabel,\n grabbedAnnouncementTemplate,\n movedAnnouncementTemplate,\n droppedAnnouncementTemplate,\n cancelledAnnouncementTemplate,\n } = props;\n const [startIndex, setStartIndex] = useState<number>(null);\n const [originalIndex, setOriginalIndex] = useState<number>(null);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [dragOver, setDragOver] = useState<number>(null);\n const [announcement, setAnnouncement] = useState('');\n const childrenArray = React.Children.toArray(children);\n const totalItems = childrenArray.length;\n\n /**\n * Replace placeholders in i18n templates\n */\n const replacePlaceholders = (\n template: string,\n data: {\n position?: number;\n grabKey?: string;\n dropKey?: string;\n altDropKey?: string;\n cancelKey?: string;\n moveKeys?: string;\n },\n ): string => {\n return template\n .replace(/\\{:position\\}/g, String(data.position ?? ''))\n .replace(/\\{:grabKey\\}/g, data.grabKey ?? 'Space')\n .replace(/\\{:dropKey\\}/g, data.dropKey ?? 'Space')\n .replace(/\\{:altDropKey\\}/g, data.altDropKey ?? 'Enter')\n .replace(/\\{:cancelKey\\}/g, data.cancelKey ?? 'Escape')\n .replace(\n /\\{:moveKeys\\}/g,\n data.moveKeys ??\n (orientation === ORIENTATION.VERTICAL ? 'Arrow Up/Down' : 'Arrow Left/Right'),\n );\n };\n\n // i18n configuration object\n const i18n = {\n itemAriaLabelTemplate: itemAriaLabelTemplate!,\n dragHandleAriaLabel: dragHandleAriaLabel!,\n grabbedAnnouncementTemplate: grabbedAnnouncementTemplate!,\n movedAnnouncementTemplate: movedAnnouncementTemplate!,\n droppedAnnouncementTemplate: droppedAnnouncementTemplate!,\n cancelledAnnouncementTemplate: cancelledAnnouncementTemplate!,\n replacePlaceholders,\n };\n\n /**\n * Drop handler invoked when a draggable item is released.\n * @param index\n */\n const drop = (index: number) => {\n if (startIndex !== null) {\n onDrop?.(startIndex, index);\n }\n setStartIndex(null);\n setOriginalIndex(null);\n setIsDragging(false);\n };\n\n /**\n * Cancel handler to restore item to original position\n */\n const cancel = () => {\n if (originalIndex !== null && startIndex !== null && startIndex !== originalIndex) {\n onDrop?.(startIndex, originalIndex);\n }\n setStartIndex(null);\n setOriginalIndex(null);\n setIsDragging(false);\n };\n\n /**\n * Start grab handler to track original position\n */\n const startGrab = (index: number) => {\n setStartIndex(index);\n setOriginalIndex(index);\n setIsDragging(true);\n };\n\n return (\n <>\n <DragContext.Provider\n value={{\n startIndex,\n setStartIndex,\n drop,\n onDrop,\n cancel,\n startGrab,\n isDragging,\n setIsDragging,\n setDragOver,\n i18n,\n }}\n >\n <Container orientation={orientation} role=\"list\">\n {React.Children.map(childrenArray, (child, index) => (\n <DragItem\n index={index}\n orientation={orientation}\n showIndicator={showIndicator}\n dragOver={dragOver}\n totalItems={totalItems}\n setAnnouncement={setAnnouncement}\n >\n {child}\n </DragItem>\n ))}\n </Container>\n </DragContext.Provider>\n <VisuallyHidden role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {announcement}\n </VisuallyHidden>\n </>\n );\n}\n\nDragAndDrop.defaultProps = {\n /** Orientation of the list layout */\n orientation: ORIENTATION.VERTICAL,\n /** Whether to display drag indicators for each list item */\n showIndicator: false,\n /** Default item aria-label template */\n itemAriaLabelTemplate:\n 'Item {:position}. Press {:grabKey} to grab, {:moveKeys} to move, {:dropKey} or {:altDropKey} to drop',\n /** Default drag handle aria-label */\n dragHandleAriaLabel: 'Drag to reorder',\n /** Default grabbed announcement template */\n grabbedAnnouncementTemplate:\n 'Item {:position} grabbed. Use {:moveKeys} to move, {:dropKey} or {:altDropKey} to drop, {:cancelKey} to cancel',\n /** Default moved announcement template */\n movedAnnouncementTemplate: 'Item moved to position {:position}',\n /** Default dropped announcement template */\n droppedAnnouncementTemplate: 'Item dropped at position {:position}',\n /** Default cancelled announcement template */\n cancelledAnnouncementTemplate: 'Drag cancelled, item restored to original position',\n};\n"]}
|
|
@@ -9,6 +9,10 @@ interface DragItemProps {
|
|
|
9
9
|
showIndicator: boolean;
|
|
10
10
|
/** The index of the item currently being dragged over */
|
|
11
11
|
dragOver: number;
|
|
12
|
+
/** Total number of items in the list */
|
|
13
|
+
totalItems: number;
|
|
14
|
+
/** Callback to set announcement for screen readers */
|
|
15
|
+
setAnnouncement: (message: string) => void;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* A draggable item component that supports both mouse and touch interactions for drag-and-drop functionality.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DragItem.d.ts","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEH,iBAAiB,EAKpB,MAAM,OAAO,CAAC;AAIf,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAEnD,UAAU,aAAa;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,EAAE,WAAW,CAAC;IACzB,0FAA0F;IAC1F,aAAa,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"DragItem.d.ts","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEH,iBAAiB,EAKpB,MAAM,OAAO,CAAC;AAIf,OAAO,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAEnD,UAAU,aAAa;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,EAAE,WAAW,CAAC;IACzB,0FAA0F;IAC1F,aAAa,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AA6DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,aAAa,CAAC,oDA2PvE"}
|
|
@@ -2,37 +2,50 @@ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
|
2
2
|
import { useContext, useState, useEffect, } from 'react';
|
|
3
3
|
import styled from '@emotion/styled';
|
|
4
4
|
import { DragIndicator } from '../../icons';
|
|
5
|
-
import
|
|
5
|
+
import { getThemeValue, THEME_NAME } from '../../shared/constants';
|
|
6
6
|
import { ORIENTATION, DragContext } from './types';
|
|
7
7
|
/** Styled component for the draggable item container */
|
|
8
8
|
const Item = styled.div `
|
|
9
9
|
cursor: ${(props) => (props.showIndicator ? 'default' : 'move')};
|
|
10
10
|
display: flex;
|
|
11
11
|
user-select: ${(props) => (props.showIndicator ? 'auto' : 'none')};
|
|
12
|
-
border-top: 2px
|
|
12
|
+
border-top: 2px dashed
|
|
13
13
|
${(props) => props.orientation === ORIENTATION.VERTICAL && props.active > 0
|
|
14
|
-
?
|
|
14
|
+
? getThemeValue(THEME_NAME.PRIMARY)
|
|
15
15
|
: 'transparent'};
|
|
16
|
-
border-bottom: 2px
|
|
16
|
+
border-bottom: 2px dashed
|
|
17
17
|
${(props) => props.orientation === ORIENTATION.VERTICAL && props.active < 0
|
|
18
|
-
?
|
|
18
|
+
? getThemeValue(THEME_NAME.PRIMARY)
|
|
19
19
|
: 'transparent'};
|
|
20
|
-
border-left: 2px
|
|
20
|
+
border-left: 2px dashed
|
|
21
21
|
${(props) => props.orientation === ORIENTATION.HORIZONTAL && props.active > 0
|
|
22
|
-
?
|
|
22
|
+
? getThemeValue(THEME_NAME.PRIMARY)
|
|
23
23
|
: 'transparent'};
|
|
24
|
-
border-right: 2px
|
|
24
|
+
border-right: 2px dashed
|
|
25
25
|
${(props) => props.orientation === ORIENTATION.HORIZONTAL && props.active < 0
|
|
26
|
-
?
|
|
26
|
+
? getThemeValue(THEME_NAME.PRIMARY)
|
|
27
27
|
: 'transparent'};
|
|
28
28
|
opacity: ${(props) => (props.dragging ? 0.5 : 1)};
|
|
29
|
+
border-radius: 10px;
|
|
30
|
+
|
|
31
|
+
&:focus {
|
|
32
|
+
box-shadow: 0 0 0 4px ${getThemeValue(THEME_NAME.PRIMARY_LIGHT)};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&:focus:not(:focus-visible) {
|
|
36
|
+
box-shadow: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:focus-visible {
|
|
40
|
+
box-shadow: 0 0 0 4px ${getThemeValue(THEME_NAME.PRIMARY_LIGHT)};
|
|
41
|
+
}
|
|
29
42
|
`;
|
|
30
43
|
/** Styled component for the drag handle indicator */
|
|
31
44
|
const DragKnob = styled.div `
|
|
32
45
|
padding-top: 8px;
|
|
33
46
|
cursor: move;
|
|
34
47
|
touch-action: none;
|
|
35
|
-
color:
|
|
48
|
+
color: ${getThemeValue(THEME_NAME.DISABLED)};
|
|
36
49
|
`;
|
|
37
50
|
/** Container for the children */
|
|
38
51
|
const Container = styled.div `
|
|
@@ -71,7 +84,7 @@ const Container = styled.div `
|
|
|
71
84
|
* @returns A draggable item with optional drag indicator and visual feedback
|
|
72
85
|
*/
|
|
73
86
|
export default function DragItem(props) {
|
|
74
|
-
const { index, orientation, children, showIndicator, dragOver } = props;
|
|
87
|
+
const { index, orientation, children, showIndicator, dragOver, totalItems, setAnnouncement } = props;
|
|
75
88
|
const [active, setActive] = useState(0);
|
|
76
89
|
const [touchTimer, setTouchTimer] = useState(null);
|
|
77
90
|
const context = useContext(DragContext);
|
|
@@ -173,6 +186,71 @@ export default function DragItem(props) {
|
|
|
173
186
|
document.body.style.overflow = 'auto';
|
|
174
187
|
}
|
|
175
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* Keyboard navigation handler for reordering items
|
|
191
|
+
* @param e Keyboard event
|
|
192
|
+
*/
|
|
193
|
+
const handleKeyDown = (e) => {
|
|
194
|
+
const isVertical = orientation === ORIENTATION.VERTICAL;
|
|
195
|
+
const moveUp = isVertical ? 'ArrowUp' : 'ArrowLeft';
|
|
196
|
+
const moveDown = isVertical ? 'ArrowDown' : 'ArrowRight';
|
|
197
|
+
const isGrabbed = context.isDragging && context.startIndex === index;
|
|
198
|
+
// Space to grab/drop
|
|
199
|
+
if (e.key === ' ' || e.key === 'Spacebar') {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
if (isGrabbed) {
|
|
202
|
+
// Drop at current position
|
|
203
|
+
context.drop(index);
|
|
204
|
+
setAnnouncement(context.i18n.replacePlaceholders(context.i18n.droppedAnnouncementTemplate, {
|
|
205
|
+
position: index + 1,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Grab item
|
|
210
|
+
context.startGrab(index);
|
|
211
|
+
setAnnouncement(context.i18n.replacePlaceholders(context.i18n.grabbedAnnouncementTemplate, {
|
|
212
|
+
position: index + 1,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Enter to drop
|
|
217
|
+
else if (e.key === 'Enter' && isGrabbed) {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
context.drop(index);
|
|
220
|
+
setAnnouncement(context.i18n.replacePlaceholders(context.i18n.droppedAnnouncementTemplate, {
|
|
221
|
+
position: index + 1,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
// Escape to cancel
|
|
225
|
+
else if (e.key === 'Escape' && isGrabbed) {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
context.cancel();
|
|
228
|
+
setAnnouncement(context.i18n.cancelledAnnouncementTemplate);
|
|
229
|
+
}
|
|
230
|
+
// Arrow keys to move while grabbed
|
|
231
|
+
else if (isGrabbed) {
|
|
232
|
+
if (e.key === moveUp && index > 0) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
// Move without dropping - just reorder and update startIndex
|
|
235
|
+
const newIndex = index - 1;
|
|
236
|
+
context.onDrop(context.startIndex, newIndex);
|
|
237
|
+
context.setStartIndex(newIndex);
|
|
238
|
+
setAnnouncement(context.i18n.replacePlaceholders(context.i18n.movedAnnouncementTemplate, {
|
|
239
|
+
position: newIndex + 1,
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
else if (e.key === moveDown && index < totalItems - 1) {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
// Move without dropping - just reorder and update startIndex
|
|
245
|
+
const newIndex = index + 1;
|
|
246
|
+
context.onDrop(context.startIndex, newIndex);
|
|
247
|
+
context.setStartIndex(newIndex);
|
|
248
|
+
setAnnouncement(context.i18n.replacePlaceholders(context.i18n.movedAnnouncementTemplate, {
|
|
249
|
+
position: newIndex + 1,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
176
254
|
/** Cleanup touch timer on unmount */
|
|
177
255
|
useEffect(() => {
|
|
178
256
|
return () => {
|
|
@@ -190,6 +268,8 @@ export default function DragItem(props) {
|
|
|
190
268
|
setActive(0);
|
|
191
269
|
}
|
|
192
270
|
}, [dragOver, context.startIndex, index, context.isDragging]);
|
|
193
|
-
return (_jsxs(Item, { draggable: !showIndicator, showIndicator: showIndicator, active: active, dragging: context.isDragging && context.startIndex === index, orientation: orientation, "data-drag-index": index,
|
|
271
|
+
return (_jsxs(Item, { draggable: !showIndicator, showIndicator: showIndicator, active: active, dragging: context.isDragging && context.startIndex === index, orientation: orientation, "data-drag-index": index, tabIndex: 0, role: "listitem", "aria-label": context.i18n.replacePlaceholders(context.i18n.itemAriaLabelTemplate, {
|
|
272
|
+
position: index + 1,
|
|
273
|
+
}), "aria-grabbed": context.isDragging && context.startIndex === index, onKeyDown: handleKeyDown, onDragStart: !showIndicator ? dragStartHandler : undefined, onDragOver: dragOverHandler, onDragLeave: dragExitHandler, onDrop: dropHandler, onTouchStart: !showIndicator ? touchStartHandler : undefined, onTouchMove: touchMoveHandler, onTouchEnd: touchEndHandler, onTouchCancel: touchEndHandler, children: [showIndicator && (_jsx(DragKnob, { draggable: true, role: "button", "aria-label": context.i18n.dragHandleAriaLabel, onDragStart: dragStartHandler, onTouchStart: touchStartHandler, onKeyDown: handleKeyDown, tabIndex: -1, children: _jsx(DragIndicator, {}) })), _jsx(Container, { children: children })] }));
|
|
194
274
|
}
|
|
195
275
|
//# sourceMappingURL=DragItem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DragItem.js","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n DragEventHandler,\n PropsWithChildren,\n useContext,\n useState,\n useEffect,\n TouchEventHandler,\n} from 'react';\nimport styled from '@emotion/styled';\nimport { DragIndicator } from '../../icons';\nimport constants from '../../shared/constants';\nimport { ORIENTATION, DragContext } from './types';\n\ninterface DragItemProps {\n /** Position index of the draggable item */\n index: number;\n /** Orientation of the drag operation (VERTICAL or HORIZONTAL) */\n orientation: ORIENTATION;\n /** Whether to show a drag handle indicator instead of making the entire item draggable */\n showIndicator: boolean;\n /** The index of the item currently being dragged over */\n dragOver: number;\n}\n\n/** Styled component for the draggable item container */\nconst Item = styled.div<{\n active: number;\n orientation: ORIENTATION;\n showIndicator: boolean;\n dragging: boolean;\n}>`\n cursor: ${(props) => (props.showIndicator ? 'default' : 'move')};\n display: flex;\n user-select: ${(props) => (props.showIndicator ? 'auto' : 'none')};\n border-top: 2px solid\n ${(props) =>\n props.orientation === ORIENTATION.VERTICAL && props.active > 0\n ? constants.PRIMARY\n : 'transparent'};\n border-bottom: 2px solid\n ${(props) =>\n props.orientation === ORIENTATION.VERTICAL && props.active < 0\n ? constants.PRIMARY\n : 'transparent'};\n border-left: 2px solid\n ${(props) =>\n props.orientation === ORIENTATION.HORIZONTAL && props.active > 0\n ? constants.PRIMARY\n : 'transparent'};\n border-right: 2px solid\n ${(props) =>\n props.orientation === ORIENTATION.HORIZONTAL && props.active < 0\n ? constants.PRIMARY\n : 'transparent'};\n opacity: ${(props) => (props.dragging ? 0.5 : 1)};\n`;\n\n/** Styled component for the drag handle indicator */\nconst DragKnob = styled.div`\n padding-top: 8px;\n cursor: move;\n touch-action: none;\n color: var(--disabled, ${constants.DISABLED});\n`;\n\n/** Container for the children */\nconst Container = styled.div`\n flex: 1;\n`;\n\n/**\n * A draggable item component that supports both mouse and touch interactions for drag-and-drop functionality.\n *\n * @component\n * @example\n * ```tsx\n * <DragItem\n * index={0}\n * orientation={ORIENTATION.VERTICAL}\n * showIndicator={true}\n * dragOver={-1}\n * >\n * <div>Draggable content</div>\n * </DragItem>\n * ```\n *\n * @param props - The component props\n * @param props.index - The position index of this item in the draggable list\n * @param props.orientation - The orientation of the drag operation (VERTICAL or HORIZONTAL)\n * @param props.showIndicator - Whether to show a drag handle indicator instead of making the entire item draggable\n * @param props.dragOver - The index of the item currently being dragged over\n * @param props.children - The content to be rendered inside the draggable item\n *\n * @remarks\n * - Uses the DragContext to manage drag state across items\n * - Provides visual feedback with borders during drag operations\n * - Supports haptic feedback (vibration) on touch devices\n * - For touch devices, requires a 200ms hold before drag starts\n * - When showIndicator is true, only the drag handle can initiate drag operations\n *\n * @returns A draggable item with optional drag indicator and visual feedback\n */\nexport default function DragItem(props: PropsWithChildren<DragItemProps>) {\n const { index, orientation, children, showIndicator, dragOver } = props;\n const [active, setActive] = useState(0);\n const [touchTimer, setTouchTimer] = useState<NodeJS.Timeout | null>(null);\n const context = useContext(DragContext);\n\n /**\n * Vibrate the device for haptic feedback\n * @param duration Duration of the vibration in milliseconds\n */\n const vibrate = (duration: number) => {\n if (navigator.vibrate) {\n navigator.vibrate(duration);\n }\n };\n\n /**\n * Drag start event handler\n * @param e Event\n */\n const dragStartHandler: DragEventHandler<HTMLDivElement> = () => {\n context.setStartIndex(index);\n context.setIsDragging(true);\n };\n\n /**\n * Drag over event handler\n * @param e Event\n */\n const dragOverHandler: DragEventHandler<HTMLDivElement> = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setActive(context.startIndex - index);\n };\n\n /**\n * Drag leave event handler\n */\n const dragExitHandler: DragEventHandler<HTMLDivElement> = () => {\n setActive(0);\n };\n\n /**\n * Drop event handler\n * @param e Event\n */\n const dropHandler: DragEventHandler<HTMLDivElement> = (e) => {\n e.preventDefault();\n setActive(0);\n context.drop(index);\n context.setIsDragging(false);\n };\n\n /**\n * Touch start event handler\n * @param e Event\n */\n const touchStartHandler: TouchEventHandler<HTMLDivElement> = () => {\n const timer = setTimeout(() => {\n context.setStartIndex(index);\n context.setIsDragging(true);\n context.setDragOver(index);\n document.body.style.overflow = 'hidden';\n vibrate(50);\n }, 200);\n\n setTouchTimer(timer);\n };\n\n /**\n * Touch move event handler\n * @param e Event\n * @returns void\n */\n const touchMoveHandler: TouchEventHandler<HTMLDivElement> = (e) => {\n const touch = e.touches[0];\n if (!touch) return;\n\n if (context.isDragging) {\n e.preventDefault();\n\n // get the element under the touch point\n const el = document.elementFromPoint(\n touch.clientX,\n touch.clientY,\n ) as HTMLElement | null;\n const overAttr = el?.closest('[data-drag-index]')?.getAttribute('data-drag-index');\n const overIndex = overAttr != null ? parseInt(overAttr, 10) : null;\n\n // if we know which index we're over, update visual state\n if (overIndex !== null) {\n context.setDragOver(overIndex);\n }\n } else if (touchTimer) {\n clearTimeout(touchTimer);\n setTouchTimer(null);\n }\n };\n\n /**\n * Touch end event handler\n * @param e Event\n */\n const touchEndHandler: TouchEventHandler<HTMLDivElement> = () => {\n if (touchTimer) {\n clearTimeout(touchTimer);\n setTouchTimer(null);\n }\n\n if (context.isDragging) {\n context.drop(dragOver);\n vibrate(50);\n context.setIsDragging(false);\n document.body.style.overflow = 'auto';\n }\n };\n\n /** Cleanup touch timer on unmount */\n useEffect(() => {\n return () => {\n if (touchTimer) clearTimeout(touchTimer);\n document.body.style.overflow = 'auto';\n };\n }, [touchTimer]);\n\n /** Update active state based on dragOver changes */\n useEffect(() => {\n if (context.isDragging && dragOver === index) {\n setActive(context.startIndex - index);\n } else {\n setActive(0);\n }\n }, [dragOver, context.startIndex, index, context.isDragging]);\n\n return (\n <Item\n draggable={!showIndicator}\n showIndicator={showIndicator}\n active={active}\n dragging={context.isDragging && context.startIndex === index}\n orientation={orientation}\n data-drag-index={index}\n onDragStart={!showIndicator ? dragStartHandler : undefined}\n onDragOver={dragOverHandler}\n onDragLeave={dragExitHandler}\n onDrop={dropHandler}\n onTouchStart={!showIndicator ? touchStartHandler : undefined}\n onTouchMove={touchMoveHandler}\n onTouchEnd={touchEndHandler}\n onTouchCancel={touchEndHandler}\n >\n {showIndicator && (\n <DragKnob draggable onDragStart={dragStartHandler} onTouchStart={touchStartHandler}>\n <DragIndicator />\n </DragKnob>\n )}\n <Container>{children}</Container>\n </Item>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DragItem.js","sourceRoot":"","sources":["../../../src/components/DragAndDrop/DragItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n DragEventHandler,\n PropsWithChildren,\n useContext,\n useState,\n useEffect,\n TouchEventHandler,\n} from 'react';\nimport styled from '@emotion/styled';\nimport { DragIndicator } from '../../icons';\nimport { getThemeValue, THEME_NAME } from '../../shared/constants';\nimport { ORIENTATION, DragContext } from './types';\n\ninterface DragItemProps {\n /** Position index of the draggable item */\n index: number;\n /** Orientation of the drag operation (VERTICAL or HORIZONTAL) */\n orientation: ORIENTATION;\n /** Whether to show a drag handle indicator instead of making the entire item draggable */\n showIndicator: boolean;\n /** The index of the item currently being dragged over */\n dragOver: number;\n /** Total number of items in the list */\n totalItems: number;\n /** Callback to set announcement for screen readers */\n setAnnouncement: (message: string) => void;\n}\n\n/** Styled component for the draggable item container */\nconst Item = styled.div<{\n active: number;\n orientation: ORIENTATION;\n showIndicator: boolean;\n dragging: boolean;\n}>`\n cursor: ${(props) => (props.showIndicator ? 'default' : 'move')};\n display: flex;\n user-select: ${(props) => (props.showIndicator ? 'auto' : 'none')};\n border-top: 2px dashed\n ${(props) =>\n props.orientation === ORIENTATION.VERTICAL && props.active > 0\n ? getThemeValue(THEME_NAME.PRIMARY)\n : 'transparent'};\n border-bottom: 2px dashed\n ${(props) =>\n props.orientation === ORIENTATION.VERTICAL && props.active < 0\n ? getThemeValue(THEME_NAME.PRIMARY)\n : 'transparent'};\n border-left: 2px dashed\n ${(props) =>\n props.orientation === ORIENTATION.HORIZONTAL && props.active > 0\n ? getThemeValue(THEME_NAME.PRIMARY)\n : 'transparent'};\n border-right: 2px dashed\n ${(props) =>\n props.orientation === ORIENTATION.HORIZONTAL && props.active < 0\n ? getThemeValue(THEME_NAME.PRIMARY)\n : 'transparent'};\n opacity: ${(props) => (props.dragging ? 0.5 : 1)};\n border-radius: 10px;\n\n &:focus {\n box-shadow: 0 0 0 4px ${getThemeValue(THEME_NAME.PRIMARY_LIGHT)};\n }\n\n &:focus:not(:focus-visible) {\n box-shadow: none;\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 4px ${getThemeValue(THEME_NAME.PRIMARY_LIGHT)};\n }\n`;\n\n/** Styled component for the drag handle indicator */\nconst DragKnob = styled.div`\n padding-top: 8px;\n cursor: move;\n touch-action: none;\n color: ${getThemeValue(THEME_NAME.DISABLED)};\n`;\n\n/** Container for the children */\nconst Container = styled.div`\n flex: 1;\n`;\n\n/**\n * A draggable item component that supports both mouse and touch interactions for drag-and-drop functionality.\n *\n * @component\n * @example\n * ```tsx\n * <DragItem\n * index={0}\n * orientation={ORIENTATION.VERTICAL}\n * showIndicator={true}\n * dragOver={-1}\n * >\n * <div>Draggable content</div>\n * </DragItem>\n * ```\n *\n * @param props - The component props\n * @param props.index - The position index of this item in the draggable list\n * @param props.orientation - The orientation of the drag operation (VERTICAL or HORIZONTAL)\n * @param props.showIndicator - Whether to show a drag handle indicator instead of making the entire item draggable\n * @param props.dragOver - The index of the item currently being dragged over\n * @param props.children - The content to be rendered inside the draggable item\n *\n * @remarks\n * - Uses the DragContext to manage drag state across items\n * - Provides visual feedback with borders during drag operations\n * - Supports haptic feedback (vibration) on touch devices\n * - For touch devices, requires a 200ms hold before drag starts\n * - When showIndicator is true, only the drag handle can initiate drag operations\n *\n * @returns A draggable item with optional drag indicator and visual feedback\n */\nexport default function DragItem(props: PropsWithChildren<DragItemProps>) {\n const { index, orientation, children, showIndicator, dragOver, totalItems, setAnnouncement } =\n props;\n const [active, setActive] = useState(0);\n const [touchTimer, setTouchTimer] = useState<NodeJS.Timeout | null>(null);\n const context = useContext(DragContext);\n\n /**\n * Vibrate the device for haptic feedback\n * @param duration Duration of the vibration in milliseconds\n */\n const vibrate = (duration: number) => {\n if (navigator.vibrate) {\n navigator.vibrate(duration);\n }\n };\n\n /**\n * Drag start event handler\n * @param e Event\n */\n const dragStartHandler: DragEventHandler<HTMLDivElement> = () => {\n context.setStartIndex(index);\n context.setIsDragging(true);\n };\n\n /**\n * Drag over event handler\n * @param e Event\n */\n const dragOverHandler: DragEventHandler<HTMLDivElement> = (e) => {\n e.preventDefault();\n e.stopPropagation();\n setActive(context.startIndex - index);\n };\n\n /**\n * Drag leave event handler\n */\n const dragExitHandler: DragEventHandler<HTMLDivElement> = () => {\n setActive(0);\n };\n\n /**\n * Drop event handler\n * @param e Event\n */\n const dropHandler: DragEventHandler<HTMLDivElement> = (e) => {\n e.preventDefault();\n setActive(0);\n context.drop(index);\n context.setIsDragging(false);\n };\n\n /**\n * Touch start event handler\n * @param e Event\n */\n const touchStartHandler: TouchEventHandler<HTMLDivElement> = () => {\n const timer = setTimeout(() => {\n context.setStartIndex(index);\n context.setIsDragging(true);\n context.setDragOver(index);\n document.body.style.overflow = 'hidden';\n vibrate(50);\n }, 200);\n\n setTouchTimer(timer);\n };\n\n /**\n * Touch move event handler\n * @param e Event\n * @returns void\n */\n const touchMoveHandler: TouchEventHandler<HTMLDivElement> = (e) => {\n const touch = e.touches[0];\n if (!touch) return;\n\n if (context.isDragging) {\n e.preventDefault();\n\n // get the element under the touch point\n const el = document.elementFromPoint(\n touch.clientX,\n touch.clientY,\n ) as HTMLElement | null;\n const overAttr = el?.closest('[data-drag-index]')?.getAttribute('data-drag-index');\n const overIndex = overAttr != null ? parseInt(overAttr, 10) : null;\n\n // if we know which index we're over, update visual state\n if (overIndex !== null) {\n context.setDragOver(overIndex);\n }\n } else if (touchTimer) {\n clearTimeout(touchTimer);\n setTouchTimer(null);\n }\n };\n\n /**\n * Touch end event handler\n * @param e Event\n */\n const touchEndHandler: TouchEventHandler<HTMLDivElement> = () => {\n if (touchTimer) {\n clearTimeout(touchTimer);\n setTouchTimer(null);\n }\n\n if (context.isDragging) {\n context.drop(dragOver);\n vibrate(50);\n context.setIsDragging(false);\n document.body.style.overflow = 'auto';\n }\n };\n\n /**\n * Keyboard navigation handler for reordering items\n * @param e Keyboard event\n */\n const handleKeyDown = (e: React.KeyboardEvent) => {\n const isVertical = orientation === ORIENTATION.VERTICAL;\n const moveUp = isVertical ? 'ArrowUp' : 'ArrowLeft';\n const moveDown = isVertical ? 'ArrowDown' : 'ArrowRight';\n\n const isGrabbed = context.isDragging && context.startIndex === index;\n\n // Space to grab/drop\n if (e.key === ' ' || e.key === 'Spacebar') {\n e.preventDefault();\n if (isGrabbed) {\n // Drop at current position\n context.drop(index);\n setAnnouncement(\n context.i18n.replacePlaceholders(context.i18n.droppedAnnouncementTemplate, {\n position: index + 1,\n }),\n );\n } else {\n // Grab item\n context.startGrab(index);\n setAnnouncement(\n context.i18n.replacePlaceholders(context.i18n.grabbedAnnouncementTemplate, {\n position: index + 1,\n }),\n );\n }\n }\n // Enter to drop\n else if (e.key === 'Enter' && isGrabbed) {\n e.preventDefault();\n context.drop(index);\n setAnnouncement(\n context.i18n.replacePlaceholders(context.i18n.droppedAnnouncementTemplate, {\n position: index + 1,\n }),\n );\n }\n // Escape to cancel\n else if (e.key === 'Escape' && isGrabbed) {\n e.preventDefault();\n context.cancel();\n setAnnouncement(context.i18n.cancelledAnnouncementTemplate);\n }\n // Arrow keys to move while grabbed\n else if (isGrabbed) {\n if (e.key === moveUp && index > 0) {\n e.preventDefault();\n // Move without dropping - just reorder and update startIndex\n const newIndex = index - 1;\n context.onDrop(context.startIndex, newIndex);\n context.setStartIndex(newIndex);\n setAnnouncement(\n context.i18n.replacePlaceholders(context.i18n.movedAnnouncementTemplate, {\n position: newIndex + 1,\n }),\n );\n } else if (e.key === moveDown && index < totalItems - 1) {\n e.preventDefault();\n // Move without dropping - just reorder and update startIndex\n const newIndex = index + 1;\n context.onDrop(context.startIndex, newIndex);\n context.setStartIndex(newIndex);\n setAnnouncement(\n context.i18n.replacePlaceholders(context.i18n.movedAnnouncementTemplate, {\n position: newIndex + 1,\n }),\n );\n }\n }\n };\n\n /** Cleanup touch timer on unmount */\n useEffect(() => {\n return () => {\n if (touchTimer) clearTimeout(touchTimer);\n document.body.style.overflow = 'auto';\n };\n }, [touchTimer]);\n\n /** Update active state based on dragOver changes */\n useEffect(() => {\n if (context.isDragging && dragOver === index) {\n setActive(context.startIndex - index);\n } else {\n setActive(0);\n }\n }, [dragOver, context.startIndex, index, context.isDragging]);\n\n return (\n <Item\n draggable={!showIndicator}\n showIndicator={showIndicator}\n active={active}\n dragging={context.isDragging && context.startIndex === index}\n orientation={orientation}\n data-drag-index={index}\n tabIndex={0}\n role=\"listitem\"\n aria-label={context.i18n.replacePlaceholders(context.i18n.itemAriaLabelTemplate, {\n position: index + 1,\n })}\n aria-grabbed={context.isDragging && context.startIndex === index}\n onKeyDown={handleKeyDown}\n onDragStart={!showIndicator ? dragStartHandler : undefined}\n onDragOver={dragOverHandler}\n onDragLeave={dragExitHandler}\n onDrop={dropHandler}\n onTouchStart={!showIndicator ? touchStartHandler : undefined}\n onTouchMove={touchMoveHandler}\n onTouchEnd={touchEndHandler}\n onTouchCancel={touchEndHandler}\n >\n {showIndicator && (\n <DragKnob\n draggable\n role=\"button\"\n aria-label={context.i18n.dragHandleAriaLabel}\n onDragStart={dragStartHandler}\n onTouchStart={touchStartHandler}\n onKeyDown={handleKeyDown}\n tabIndex={-1}\n >\n <DragIndicator />\n </DragKnob>\n )}\n <Container>{children}</Container>\n </Item>\n );\n}\n"]}
|