@spaced-out/ui-design-system 0.6.36 → 0.6.37
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/.cspell/custom-words.txt +9 -0
- package/CHANGELOG.md +9 -0
- package/lib/components/ButtonDropdown/ButtonDropdown.d.ts +10 -0
- package/lib/components/ButtonDropdown/ButtonDropdown.d.ts.map +1 -1
- package/lib/components/ButtonDropdown/ButtonDropdown.js +26 -3
- package/lib/components/ButtonTabs/ButtonTab/ButtonTab.d.ts +9 -1
- package/lib/components/ButtonTabs/ButtonTab/ButtonTab.d.ts.map +1 -1
- package/lib/components/ButtonTabs/ButtonTab/ButtonTab.js +8 -0
- package/lib/components/ButtonTabs/ButtonTabDropdown.d.ts +10 -1
- package/lib/components/ButtonTabs/ButtonTabDropdown.d.ts.map +1 -1
- package/lib/components/ButtonTabs/ButtonTabDropdown.js +31 -3
- package/lib/components/ButtonTabs/ButtonTabs.d.ts +6 -0
- package/lib/components/ButtonTabs/ButtonTabs.d.ts.map +1 -1
- package/lib/components/ButtonTabs/ButtonTabs.js +2 -0
- package/lib/components/Combobox/Combobox.d.ts +9 -0
- package/lib/components/Combobox/Combobox.d.ts.map +1 -1
- package/lib/components/Combobox/Combobox.js +21 -0
- package/lib/components/Combobox/helper.d.ts.map +1 -1
- package/lib/components/Combobox/helper.js +0 -4
- package/lib/components/Dropdown/Dropdown.d.ts +9 -0
- package/lib/components/Dropdown/Dropdown.d.ts.map +1 -1
- package/lib/components/Dropdown/Dropdown.js +92 -24
- package/lib/components/Dropdown/Dropdown.module.css +24 -0
- package/lib/components/InlineDropdown/InlineDropdown.d.ts +10 -0
- package/lib/components/InlineDropdown/InlineDropdown.d.ts.map +1 -1
- package/lib/components/InlineDropdown/InlineDropdown.js +23 -3
- package/lib/components/Input/Input.module.css +38 -0
- package/lib/components/Menu/Menu.d.ts +99 -0
- package/lib/components/Menu/Menu.d.ts.map +1 -1
- package/lib/components/Menu/Menu.js +1152 -187
- package/lib/components/Menu/Menu.module.css +191 -7
- package/lib/components/Menu/MenuFocusScope.d.ts +21 -0
- package/lib/components/Menu/MenuFocusScope.d.ts.map +1 -0
- package/lib/components/Menu/MenuFocusScope.js +37 -0
- package/lib/components/Menu/MenuOptionButton.d.ts +36 -0
- package/lib/components/Menu/MenuOptionButton.d.ts.map +1 -1
- package/lib/components/Menu/MenuOptionButton.js +137 -29
- package/lib/components/Menu/index.d.ts +2 -0
- package/lib/components/Menu/index.d.ts.map +1 -1
- package/lib/components/Menu/index.js +22 -0
- package/lib/components/Menu/menuOptionId.d.ts +12 -0
- package/lib/components/Menu/menuOptionId.d.ts.map +1 -0
- package/lib/components/Menu/menuOptionId.js +18 -0
- package/lib/components/Menu/useMenuKeyboardNavigation.d.ts +93 -0
- package/lib/components/Menu/useMenuKeyboardNavigation.d.ts.map +1 -0
- package/lib/components/Menu/useMenuKeyboardNavigation.js +471 -0
- package/lib/components/Menu/useMenuTrigger.d.ts +207 -0
- package/lib/components/Menu/useMenuTrigger.d.ts.map +1 -0
- package/lib/components/Menu/useMenuTrigger.js +389 -0
- package/lib/components/OptionButton/OptionButton.d.ts +16 -0
- package/lib/components/OptionButton/OptionButton.d.ts.map +1 -1
- package/lib/components/OptionButton/OptionButton.js +4 -1
- package/lib/components/Tabs/Tab/Tab.d.ts +8 -1
- package/lib/components/Tabs/Tab/Tab.d.ts.map +1 -1
- package/lib/components/Tabs/Tab/Tab.js +8 -0
- package/lib/components/Tabs/TabList/TabDropdown.d.ts +10 -1
- package/lib/components/Tabs/TabList/TabDropdown.d.ts.map +1 -1
- package/lib/components/Tabs/TabList/TabDropdown.js +23 -3
- package/lib/components/Tabs/TabList/TabList.d.ts +14 -0
- package/lib/components/Tabs/TabList/TabList.d.ts.map +1 -1
- package/lib/components/Tabs/TabList/TabList.js +6 -0
- package/lib/components/Typeahead/Typeahead.d.ts +9 -0
- package/lib/components/Typeahead/Typeahead.d.ts.map +1 -1
- package/lib/components/Typeahead/Typeahead.js +107 -13
- package/lib/components/Typeahead/Typeahead.module.css +13 -0
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.d.ts.map +1 -1
- package/lib/hooks/index.js +11 -0
- package/lib/hooks/useInteractionModality/index.d.ts +2 -0
- package/lib/hooks/useInteractionModality/index.d.ts.map +1 -0
- package/lib/hooks/useInteractionModality/index.js +16 -0
- package/lib/hooks/useInteractionModality/useInteractionModality.d.ts +39 -0
- package/lib/hooks/useInteractionModality/useInteractionModality.d.ts.map +1 -0
- package/lib/hooks/useInteractionModality/useInteractionModality.js +111 -0
- package/lib/utils/click-away/click-away.d.ts +16 -0
- package/lib/utils/click-away/click-away.d.ts.map +1 -1
- package/lib/utils/click-away/click-away.js +49 -0
- package/mcp/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useInteractionModality = exports.startInteractionModalityTracking = exports.getInteractionModality = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
/*
|
|
9
|
+
* Input modality is a property of the SESSION, not of any one component, so it
|
|
10
|
+
* lives in module scope rather than in state that each consumer would have to
|
|
11
|
+
* thread around. This is the same shape the platform itself uses: `:focus-visible`
|
|
12
|
+
* is implemented by the engine tracking the last interaction globally.
|
|
13
|
+
*
|
|
14
|
+
* Reach for this only when CSS cannot answer the question — `:focus-visible` is
|
|
15
|
+
* the right tool whenever the element you need to style is the one that was
|
|
16
|
+
* interacted with. This exists for the case where it is NOT: a popup deciding
|
|
17
|
+
* whether to show a keyboard cursor has to know how its TRIGGER was operated,
|
|
18
|
+
* and the trigger's own `:focus-visible` cannot be trusted for that, because
|
|
19
|
+
* browsers always match it on text inputs (typing is expected there) — so an
|
|
20
|
+
* input-triggered popup would always look keyboard-opened.
|
|
21
|
+
*/
|
|
22
|
+
let currentModality = 'pointer';
|
|
23
|
+
let listening = false;
|
|
24
|
+
const subscribers = new Set();
|
|
25
|
+
|
|
26
|
+
// Keys that navigate even when chorded with a modifier — Cmd+Down is "jump to
|
|
27
|
+
// the end" on macOS and the menus honour it as such.
|
|
28
|
+
const NAVIGATION_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Tab', 'Enter', ' ']);
|
|
29
|
+
const MODIFIER_KEYS = new Set(['Shift', 'Meta', 'Alt', 'Control']);
|
|
30
|
+
const set = modality => {
|
|
31
|
+
if (currentModality === modality) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
currentModality = modality;
|
|
35
|
+
subscribers.forEach(notify => notify(modality));
|
|
36
|
+
};
|
|
37
|
+
const handleKeyDown = event => {
|
|
38
|
+
// A modifier pressed on its own — Shift held for a click, say — is not an
|
|
39
|
+
// interaction, and a chorded shortcut (Cmd+C, Ctrl+S) is a command rather than
|
|
40
|
+
// navigation. Neither should make the session look keyboard-driven. A chorded
|
|
41
|
+
// NAVIGATION key (Cmd+Down) does count: it moves the highlight.
|
|
42
|
+
if (MODIFIER_KEYS.has(event.key)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if ((event.metaKey || event.altKey || event.ctrlKey) && !NAVIGATION_KEYS.has(event.key)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
set('keyboard');
|
|
49
|
+
};
|
|
50
|
+
const handlePointerDown = () => set('pointer');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Begin tracking. Idempotent, and session-scoped by design: the listeners live
|
|
54
|
+
* for the life of the page, so there is nothing to tear down.
|
|
55
|
+
*
|
|
56
|
+
* Tracking has to start BEFORE the thing that wants the answer exists. The
|
|
57
|
+
* interesting question is "how was the popup that just mounted opened?", and the
|
|
58
|
+
* keypress that opened it happened while the popup did not yet exist — so a
|
|
59
|
+
* component reading this on mount is always too late to have attached the
|
|
60
|
+
* listener itself. Whatever renders earlier and knows a popup may follow — a
|
|
61
|
+
* menu trigger — primes it.
|
|
62
|
+
*
|
|
63
|
+
* It is not primed on import. Attaching document listeners is a side effect, and
|
|
64
|
+
* a consumer that has not opted into any of this should get none: an app merely
|
|
65
|
+
* importing a component from this package must not start listening to its
|
|
66
|
+
* keyboard. Capture phase, so the modality is recorded before any component
|
|
67
|
+
* handler reacts to the same event.
|
|
68
|
+
*/
|
|
69
|
+
const startInteractionModalityTracking = () => {
|
|
70
|
+
if (typeof document === 'undefined' || listening) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
listening = true;
|
|
74
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
75
|
+
document.addEventListener('pointerdown', handlePointerDown, true);
|
|
76
|
+
};
|
|
77
|
+
exports.startInteractionModalityTracking = startInteractionModalityTracking;
|
|
78
|
+
const subscribe = notify => {
|
|
79
|
+
startInteractionModalityTracking();
|
|
80
|
+
subscribers.add(notify);
|
|
81
|
+
return () => {
|
|
82
|
+
subscribers.delete(notify);
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* How the user last interacted with the page, read imperatively.
|
|
88
|
+
*
|
|
89
|
+
* Use this when the answer is needed during render or in an effect rather than
|
|
90
|
+
* as reactive state — for example a popup deciding on mount whether it was opened
|
|
91
|
+
* by keyboard, where re-rendering on later modality changes would be wrong.
|
|
92
|
+
*/
|
|
93
|
+
const getInteractionModality = () => currentModality;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* How the user last interacted with the page, as reactive state. Re-renders the
|
|
97
|
+
* caller when the user switches between keyboard and pointer.
|
|
98
|
+
*/
|
|
99
|
+
exports.getInteractionModality = getInteractionModality;
|
|
100
|
+
const useInteractionModality = function () {
|
|
101
|
+
let enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
102
|
+
const [modality, setModality] = (0, _react.useState)(() => getInteractionModality());
|
|
103
|
+
(0, _react.useEffect)(() => {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
return subscribe(setModality);
|
|
108
|
+
}, [enabled]);
|
|
109
|
+
return enabled ? modality : 'pointer';
|
|
110
|
+
};
|
|
111
|
+
exports.useInteractionModality = useInteractionModality;
|
|
@@ -16,6 +16,19 @@ export interface ClickAwayRefType {
|
|
|
16
16
|
}
|
|
17
17
|
export interface ClickAwayProps<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> {
|
|
18
18
|
closeOnEscapeKeypress?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Return focus to whatever had it before the popup opened, when the popup
|
|
21
|
+
* closes with focus still inside it (Escape, a selection, Tab past the edge).
|
|
22
|
+
* A click elsewhere on the page is exempt: focus belongs where the user
|
|
23
|
+
* clicked.
|
|
24
|
+
*
|
|
25
|
+
* Off by default. It is only wanted where something moves focus INTO the
|
|
26
|
+
* popup — otherwise focus never left the trigger and there is nothing to
|
|
27
|
+
* restore, and calling `focus()` on a trigger that already has it can be
|
|
28
|
+
* enough to disturb a consumer (a field that opens its own menu on focus
|
|
29
|
+
* would reopen the popup that just closed).
|
|
30
|
+
*/
|
|
31
|
+
restoreFocusOnClose?: boolean;
|
|
19
32
|
children: (props: ChildProps<TTrigger, TBoundary>) => React.ReactNode;
|
|
20
33
|
onChange?: (isOpen: boolean) => unknown;
|
|
21
34
|
clickAwayRef?: React.RefObject<ClickAwayRefType | null>;
|
|
@@ -56,11 +69,14 @@ export declare class ClickAway<TTrigger extends HTMLElement = HTMLButtonElement,
|
|
|
56
69
|
el: HTMLElement | null | undefined;
|
|
57
70
|
boundaryRef: BoundaryRefType<TBoundary>;
|
|
58
71
|
triggerRef: TriggerRefType<TTrigger>;
|
|
72
|
+
previouslyFocused: HTMLElement | null;
|
|
73
|
+
closedByOutsideClick: boolean;
|
|
59
74
|
componentDidMount(): void;
|
|
60
75
|
componentDidUpdate(_prevProps: Readonly<ClickAwayProps<TTrigger, TBoundary>>, prevState: Readonly<ClickAwayState>): void;
|
|
61
76
|
componentWillUnmount(): void;
|
|
62
77
|
render(): React.ReactNode;
|
|
63
78
|
handleOpenClick: () => void;
|
|
79
|
+
restoreFocus: () => void;
|
|
64
80
|
handleCloseClick: (evt: MouseEvent) => void;
|
|
65
81
|
handleCloseOnEscapeKeypress: (evt?: KeyboardEvent) => void;
|
|
66
82
|
forceClose: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"click-away.d.ts","sourceRoot":"","sources":["../../../src/utils/click-away/click-away.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,KAAK,eAAe,CAAC,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAExE,KAAK,cAAc,CAAC,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAEvE,MAAM,WAAW,UAAU,CACzB,QAAQ,SAAS,WAAW,GAAG,iBAAiB,EAChD,SAAS,SAAS,WAAW,GAAG,cAAc;IAE9C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACxC,UAAU,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,cAAc,CAC7B,QAAQ,SAAS,WAAW,GAAG,iBAAiB,EAChD,SAAS,SAAS,WAAW,GAAG,cAAc;IAE9C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IACxC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACxD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACvC;AAID,qBAAa,SAAS,CACpB,QAAQ,SAAS,WAAW,GAAG,iBAAiB,EAChD,SAAS,SAAS,WAAW,GAAG,cAAc,CAC9C,SAAQ,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC5E,MAAM,CAAC,YAAY,EAAE;QACnB,6BAA6B,CAAC,EAAE,OAAO,CAAC;QACxC,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,CAGC;IAEF,KAAK,EAAE,cAAc,CAInB;IAEF,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAQ;IAC1C,WAAW,EAAE,eAAe,CAAC,SAAS,CAAC,CAAgC;IACvE,UAAU,EAAE,cAAc,CAAC,QAAQ,CAAC,CAA+B;
|
|
1
|
+
{"version":3,"file":"click-away.d.ts","sourceRoot":"","sources":["../../../src/utils/click-away/click-away.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,17 @@ class ClickAway extends React.Component {
|
|
|
24
24
|
el = null;
|
|
25
25
|
boundaryRef = /*#__PURE__*/React.createRef();
|
|
26
26
|
triggerRef = /*#__PURE__*/React.createRef();
|
|
27
|
+
// Element that had focus right before the popup opened. Restored on close so
|
|
28
|
+
// keyboard focus returns to the trigger (Escape, a selection, Tab past the
|
|
29
|
+
// popup's edge) — the job FloatingFocusManager's returnFocus does for the
|
|
30
|
+
// dropdown family until they move onto this.
|
|
31
|
+
previouslyFocused = null;
|
|
32
|
+
// Set by handleCloseClick for the close that follows. A click on the page is
|
|
33
|
+
// the user moving on: focus belongs wherever they clicked (or on <body>), and
|
|
34
|
+
// pulling it back onto the trigger would re-run the trigger's own focus
|
|
35
|
+
// handling — a Typeahead opens its menu on focus, so it would reopen the very
|
|
36
|
+
// popup the click dismissed.
|
|
37
|
+
closedByOutsideClick = false;
|
|
27
38
|
componentDidMount() {
|
|
28
39
|
if (this.el) {
|
|
29
40
|
this.setState({
|
|
@@ -49,6 +60,12 @@ class ClickAway extends React.Component {
|
|
|
49
60
|
capture: true
|
|
50
61
|
});
|
|
51
62
|
window.document.removeEventListener('keyup', this.handleCloseOnEscapeKeypress);
|
|
63
|
+
if (this.closedByOutsideClick || !this.props.restoreFocusOnClose) {
|
|
64
|
+
this.previouslyFocused = null;
|
|
65
|
+
} else {
|
|
66
|
+
this.restoreFocus();
|
|
67
|
+
}
|
|
68
|
+
this.closedByOutsideClick = false;
|
|
52
69
|
}
|
|
53
70
|
}
|
|
54
71
|
}
|
|
@@ -92,11 +109,42 @@ class ClickAway extends React.Component {
|
|
|
92
109
|
if (this.el) {
|
|
93
110
|
pageBottom = this.pageBottom();
|
|
94
111
|
}
|
|
112
|
+
|
|
113
|
+
// Capture the focused element before opening (still the trigger at this
|
|
114
|
+
// point, before the popup mounts and moves focus into itself) so it can be
|
|
115
|
+
// restored on close.
|
|
116
|
+
if (this.props.restoreFocusOnClose && !this.state.isOpen && typeof window.document !== 'undefined') {
|
|
117
|
+
this.previouslyFocused = window.document.activeElement;
|
|
118
|
+
}
|
|
95
119
|
this.setState({
|
|
96
120
|
isOpen: !this.state.isOpen,
|
|
97
121
|
pageBottom
|
|
98
122
|
}, this.handleOnChange);
|
|
99
123
|
};
|
|
124
|
+
|
|
125
|
+
// Return focus to the trigger when the popup closes — but only if focus was
|
|
126
|
+
// lost to the document body (the focused popup element was unmounted). If the
|
|
127
|
+
// user moved focus elsewhere (e.g. clicked another control), leave it there.
|
|
128
|
+
restoreFocus = () => {
|
|
129
|
+
const previous = this.previouslyFocused;
|
|
130
|
+
this.previouslyFocused = null;
|
|
131
|
+
if (typeof window.document === 'undefined') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const active = window.document.activeElement;
|
|
135
|
+
const focusLost = !active || active === window.document.body;
|
|
136
|
+
if (!focusLost) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// `previouslyFocused` is <body> whenever the opening click did not land on a
|
|
140
|
+
// natively focusable element — notably Safari and Firefox on macOS, which do
|
|
141
|
+
// not focus a <button> on click. Restoring it would be a no-op that strands
|
|
142
|
+
// focus at the top of the document, so fall back to the trigger itself.
|
|
143
|
+
const target = previous && previous !== window.document.body ? previous : this.triggerRef.current;
|
|
144
|
+
if (target && window.document.contains(target)) {
|
|
145
|
+
target.focus();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
100
148
|
handleCloseClick = evt => {
|
|
101
149
|
if (evt.target instanceof Node && this.props.containsNestedFloatingPortals && this.boundaryRef.current?.parentElement && (this.boundaryRef.current.parentElement === evt.target || this.boundaryRef.current.parentElement.contains(evt.target))) {
|
|
102
150
|
return;
|
|
@@ -107,6 +155,7 @@ class ClickAway extends React.Component {
|
|
|
107
155
|
if (evt.target instanceof Node && this.triggerRef && (this.triggerRef.current === evt.target || this.triggerRef.current?.contains(evt.target))) {
|
|
108
156
|
return;
|
|
109
157
|
}
|
|
158
|
+
this.closedByOutsideClick = true;
|
|
110
159
|
this.setState({
|
|
111
160
|
isOpen: false
|
|
112
161
|
}, this.handleOnChange);
|
package/mcp/package.json
CHANGED