{
disconnectedCallback();
+ return cleanup;
}, [ref, read, write, callback]);
};
diff --git a/packages/design-system/src/components/primitives/create-content-controller.ts b/packages/design-system/src/components/primitives/create-content-controller.ts
index 4d1ea083ac..4b3cdaf425 100644
--- a/packages/design-system/src/components/primitives/create-content-controller.ts
+++ b/packages/design-system/src/components/primitives/create-content-controller.ts
@@ -2,11 +2,11 @@
* @description
* - detects whether the pointer/keyboard is on any given token as specified by the {contents: []} contract.
* - pointermove/keyup in turn dispatch onMouseMove/onCaretMove with the current state if and when within a configured content token.
- * - returns two methods {update, disconnectedCallback} the former of which dispatches a content update(while preserving the caret position if any)
+ * - returns two methods {update, cleanup} the former of which dispatches a content update(while preserving the caret position if any)
* - read/write methods are called when reading from the passed element or when updating/writing to the same element.
* @example
* const target = document.querySelector('input');
- * const {update, disconnectedCallback} = createContentController(target, {
+ * const {update, cleanup} = createContentController(target, {
* contents: [
* { name: 'unit', match: (value) => value === 'px' },
* { name: 'number', match: (value) => Number.isNaN(parseFloat(value)) === false },
@@ -89,7 +89,7 @@ export const createContentController = (
}
): {
update: (value: string) => void;
- disconnectedCallback: () => void;
+ cleanup: () => void;
} => {
const eventNames = ["keyup", "pointermove"] as const;
const handleEvent = (event: KeyboardEvent | PointerEvent): void => {
@@ -136,7 +136,7 @@ export const createContentController = (
write(targetNode, value);
targetNode.setSelectionRange(selectionStart, selectionEnd);
},
- disconnectedCallback: () => {
+ cleanup: () => {
eventNames.forEach((eventName) =>
targetNode.removeEventListener(eventName, handleEvent, false)
);
diff --git a/packages/design-system/src/components/primitives/numeric-gesture-control.stories.tsx b/packages/design-system/src/components/primitives/numeric-gesture-control.stories.tsx
index e748f9a5ba..51b49525f2 100644
--- a/packages/design-system/src/components/primitives/numeric-gesture-control.stories.tsx
+++ b/packages/design-system/src/components/primitives/numeric-gesture-control.stories.tsx
@@ -19,8 +19,8 @@ const useNumericScrubControl = ({
return;
}
ref.current.value = String(value);
- const { disconnectedCallback } = numericScrubControl(ref.current, {
- getValue: () => value,
+ return numericScrubControl(ref.current, {
+ getInitialValue: () => value,
direction: direction,
onValueChange: (event) => {
event.preventDefault();
@@ -28,7 +28,6 @@ const useNumericScrubControl = ({
(event.target as HTMLInputElement).select();
},
});
- return () => disconnectedCallback();
}, [direction, value, ref]);
};
diff --git a/packages/design-system/src/components/primitives/numeric-gesture-control.ts b/packages/design-system/src/components/primitives/numeric-gesture-control.ts
index f1d32770ce..5c174f35df 100644
--- a/packages/design-system/src/components/primitives/numeric-gesture-control.ts
+++ b/packages/design-system/src/components/primitives/numeric-gesture-control.ts
@@ -25,9 +25,15 @@ export type NumericScrubCallback = (event: {
}) => void;
export type NumericScrubOptions = {
+ inverse?: boolean;
minValue?: NumericScrubValue;
maxValue?: NumericScrubValue;
- getValue: () => number | undefined;
+ getInitialValue: () => number | undefined;
+ getValue?: (
+ state: NumericScrubState,
+ movement: number,
+ options: NumericScrubOptions
+ ) => number;
direction?: NumericScrubDirection;
onValueInput?: NumericScrubCallback;
onValueChange?: NumericScrubCallback;
@@ -42,19 +48,37 @@ type NumericScrubState = {
timerId?: ReturnType;
};
-export const numericScrubControl = (
- targetNode: HTMLElement | SVGElement,
+const getValueDefault = (
+ state: NumericScrubState,
+ movement: number,
{
minValue = Number.MIN_SAFE_INTEGER,
maxValue = Number.MAX_SAFE_INTEGER,
- getValue,
+ }: NumericScrubOptions
+) => {
+ const value = state.value + movement;
+ if (value < minValue) {
+ return minValue;
+ }
+ if (state.value > maxValue) {
+ return maxValue;
+ }
+ return value;
+};
+
+export const numericScrubControl = (
+ targetNode: HTMLElement | SVGElement,
+ options: NumericScrubOptions
+) => {
+ const {
+ getInitialValue,
+ getValue = getValueDefault,
direction = "horizontal",
onValueInput,
onValueChange,
onStatusChange,
shouldHandleEvent,
- }: NumericScrubOptions
-) => {
+ } = options;
const eventNames = ["pointerup", "pointerdown"] as const;
const state: NumericScrubState = {
// We will read value lazyly in a moment it will be used to avoid having outdated value
@@ -66,6 +90,25 @@ export const numericScrubControl = (
let exitPointerLock: (() => void) | undefined = undefined;
+ let originalUserSelect = "";
+
+ const cleanup = () => {
+ targetNode.removeEventListener("pointermove", handleEvent);
+ onStatusChange?.("idle");
+ clearTimeout(state.timerId);
+
+ exitPointerLock?.();
+ exitPointerLock = undefined;
+ if (originalUserSelect) {
+ targetNode.ownerDocument.documentElement.style.userSelect =
+ originalUserSelect;
+ } else {
+ targetNode.ownerDocument.documentElement.style.removeProperty(
+ "user-select"
+ );
+ }
+ };
+
// Cannot define `event:` as PointerEvent,
// because (HTMLElement | SVGElement).addEventListener("pointermove", ...)
// takes (Event => void) as a callback
@@ -81,13 +124,7 @@ export const numericScrubControl = (
switch (type) {
case "pointerup": {
const shouldComponentUpdate = Boolean(state.cursor);
- targetNode.removeEventListener("pointermove", handleEvent);
- onStatusChange?.("idle");
- clearTimeout(state.timerId);
-
- exitPointerLock?.();
- exitPointerLock = undefined;
-
+ cleanup();
if (shouldComponentUpdate) {
onValueChange?.({
target: targetNode,
@@ -108,11 +145,11 @@ export const numericScrubControl = (
if (event.pressure === 0 || event.button !== 0) {
break;
}
- const value = getValue();
+ const value = getInitialValue();
// We don't support scrub on non unit values
// Its highly unlikely that the value here will be undefined, as useScrub tries to not create scrub on non unit values
- // but having that we use lazy getValue() and vanilla js events it's possible.
+ // but having that we use lazy getInitialValue() and vanilla js events it's possible.
if (value === undefined) {
return;
}
@@ -127,17 +164,17 @@ export const numericScrubControl = (
onStatusChange?.("scrubbing");
targetNode.addEventListener("pointermove", handleEvent);
+ originalUserSelect =
+ targetNode.ownerDocument.documentElement.style.userSelect;
+ targetNode.ownerDocument.documentElement.style.userSelect = "none";
break;
}
case "pointermove": {
- state.value += movement;
-
- if (state.value < minValue) {
- state.value = minValue;
- } else if (state.value > maxValue) {
- state.value = maxValue;
+ const nextValue = getValue(state, movement, options);
+ if (nextValue === state.value) {
+ break;
}
-
+ state.value = nextValue;
onValueInput?.({
target: targetNode,
value: state.value,
@@ -172,19 +209,11 @@ export const numericScrubControl = (
targetNode.addEventListener(eventName, handleEvent)
);
- return {
- disconnectedCallback: () => {
- eventNames.forEach((eventName) =>
- targetNode.removeEventListener(eventName, handleEvent)
- );
-
- clearTimeout(state.timerId);
- targetNode.removeEventListener("pointermove", handleEvent);
- onStatusChange?.("idle");
-
- exitPointerLock?.();
- exitPointerLock = undefined;
- },
+ return () => {
+ eventNames.forEach((eventName) =>
+ targetNode.removeEventListener(eventName, handleEvent)
+ );
+ cleanup();
};
};
diff --git a/packages/design-system/src/components/primitives/use-scrub.ts b/packages/design-system/src/components/primitives/use-scrub.ts
index ca2bc52a29..355a4d623a 100644
--- a/packages/design-system/src/components/primitives/use-scrub.ts
+++ b/packages/design-system/src/components/primitives/use-scrub.ts
@@ -40,8 +40,8 @@ export const useScrub = ({
return;
}
- const scrub = numericScrubControl(scrubRefCurrent, {
- getValue: () => {
+ return numericScrubControl(scrubRefCurrent, {
+ getInitialValue: () => {
return valueRef.current;
},
onValueInput(event) {
@@ -66,8 +66,6 @@ export const useScrub = ({
},
shouldHandleEvent: shouldHandleEventRef.current,
});
-
- return scrub.disconnectedCallback;
}, []);
return { scrubRef, inputRef };