@warp-ds/core
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -1,21 +0,5 @@ | ||
type Directions = "top" | "right" | "bottom" | "left"; | ||
export declare const opposites: { | ||
top: string; | ||
bottom: string; | ||
left: string; | ||
right: string; | ||
}; | ||
export declare const arrowLabels: { | ||
top: string; | ||
bottom: string; | ||
left: string; | ||
right: string; | ||
}; | ||
export declare const directions: string[]; | ||
export declare const rotation: { | ||
left: number; | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
}; | ||
import { ReferenceElement } from '@floating-ui/dom'; | ||
export type Directions = 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'; | ||
export declare const directions: Directions[]; | ||
export declare const opposites: Record<Directions, Directions>; | ||
export type AttentionState = { | ||
@@ -25,17 +9,15 @@ isShowing?: boolean; | ||
actualDirection?: Directions; | ||
directionName: Directions; | ||
directionName?: Directions; | ||
arrowEl?: HTMLElement | null; | ||
attentionEl?: HTMLElement | null; | ||
targetEl?: unknown; | ||
top?: Boolean; | ||
right?: Boolean; | ||
bottom?: Boolean; | ||
left?: Boolean; | ||
tooltip?: Boolean; | ||
popover?: Boolean; | ||
callout?: Boolean; | ||
flip?: Boolean; | ||
fallbackPlacements?: Directions[]; | ||
targetEl?: ReferenceElement | null; | ||
noArrow?: Boolean; | ||
distance?: number; | ||
skidding?: number; | ||
waitForDOM?: () => void; | ||
}; | ||
export declare function useRecompute(state: AttentionState): Promise<void>; | ||
export {}; | ||
export declare const arrowDirectionClassname: (dir: Directions) => Directions; | ||
export declare function useRecompute(state: AttentionState): Promise<void | AttentionState>; | ||
export declare const autoUpdatePosition: (state: AttentionState) => (() => void) | undefined; |
@@ -1,28 +0,85 @@ | ||
import { computePosition, flip, offset, shift, arrow } from "@floating-ui/dom"; | ||
const TOP = "top"; | ||
const BOTTOM = "bottom"; | ||
const LEFT = "left"; | ||
const RIGHT = "right"; | ||
import { computePosition, flip, offset, shift, arrow, autoUpdate, autoPlacement, } from '@floating-ui/dom'; | ||
const TOP_START = 'top-start'; | ||
const TOP = 'top'; | ||
const TOP_END = 'top-end'; | ||
const RIGHT_START = 'right-start'; | ||
const RIGHT = 'right'; | ||
const RIGHT_END = 'right-end'; | ||
const BOTTOM_START = 'bottom-start'; | ||
const BOTTOM = 'bottom'; | ||
const BOTTOM_END = 'bottom-end'; | ||
const LEFT_START = 'left-start'; | ||
const LEFT = 'left'; | ||
const LEFT_END = 'left-end'; | ||
export const directions = [ | ||
TOP_START, | ||
TOP, | ||
TOP_END, | ||
RIGHT_START, | ||
RIGHT, | ||
RIGHT_END, | ||
BOTTOM_START, | ||
BOTTOM, | ||
BOTTOM_END, | ||
LEFT_START, | ||
LEFT, | ||
LEFT_END | ||
]; | ||
export const opposites = { | ||
[TOP_START]: BOTTOM_START, | ||
[TOP]: BOTTOM, | ||
[TOP_END]: BOTTOM_END, | ||
[BOTTOM_START]: TOP_START, | ||
[BOTTOM]: TOP, | ||
[BOTTOM_END]: TOP_END, | ||
[LEFT_START]: RIGHT_START, | ||
[LEFT]: RIGHT, | ||
[LEFT_END]: RIGHT_END, | ||
[RIGHT_START]: LEFT_START, | ||
[RIGHT]: LEFT, | ||
[RIGHT_END]: LEFT_END, | ||
}; | ||
export const arrowLabels = { | ||
[TOP]: "↑", | ||
[BOTTOM]: "↓", | ||
[LEFT]: "←", | ||
[RIGHT]: "→", | ||
}; | ||
export const directions = [TOP, BOTTOM, LEFT, RIGHT]; | ||
export const rotation = { | ||
const rotation = { | ||
[LEFT_START]: -45, | ||
[LEFT]: -45, | ||
[LEFT_END]: -45, | ||
[TOP_START]: 45, | ||
[TOP]: 45, | ||
[TOP_END]: 45, | ||
[RIGHT_START]: 135, | ||
[RIGHT]: 135, | ||
[RIGHT_END]: 135, | ||
[BOTTOM_START]: -135, | ||
[BOTTOM]: -135, | ||
[BOTTOM_END]: -135, | ||
}; | ||
const middlePosition = "calc(50% - 7px)"; | ||
const isDirectionVertical = (name) => [TOP, BOTTOM].includes(name); | ||
function computeCalloutArrow({ actualDirection, directionName, arrowEl, }) { | ||
const middlePosition = 'calc(50% - 7px)'; | ||
const isDirectionVertical = (name) => [TOP_START, TOP, TOP_END, BOTTOM_START, BOTTOM, BOTTOM_END].includes(name); | ||
export const arrowDirectionClassname = (dir) => { | ||
let direction; | ||
if (/-/.test(dir)) { | ||
direction = dir | ||
.split('-') | ||
.map((d) => d.charAt(0).toUpperCase() + d.slice(1)) | ||
.join(''); | ||
} | ||
else { | ||
direction = dir.charAt(0).toUpperCase() + dir.slice(1); | ||
} | ||
return direction; | ||
}; | ||
const side = (dir) => dir.split('-')[0]; | ||
const staticSide = (dir) => opposites[side(dir)]; | ||
const arrowDirection = (dir) => opposites[dir]; | ||
const arrowRotation = (dir) => rotation[arrowDirection(dir)]; | ||
const applyArrowStyles = (arrowEl, arrowRotation, dir) => { | ||
Object.assign(arrowEl?.style, { | ||
borderTopLeftRadius: '4px', | ||
zIndex: 1, | ||
// border alignment is off by a fraction of a pixel, this fixes it | ||
[`margin${arrowDirectionClassname(staticSide(dir))}`]: '-0.5px', | ||
transform: `rotate(${arrowRotation}deg)`, | ||
}); | ||
}; | ||
function computeCalloutArrow({ actualDirection, directionName = BOTTOM, arrowEl, }) { | ||
if (!arrowEl) | ||
@@ -32,35 +89,94 @@ return; | ||
const directionIsVertical = isDirectionVertical(directionName); | ||
arrowEl.style.left = directionIsVertical ? middlePosition : ""; | ||
arrowEl.style.top = !directionIsVertical ? middlePosition : ""; | ||
Object.assign(arrowEl?.style || {}, { | ||
left: directionIsVertical ? middlePosition : '', | ||
top: !directionIsVertical ? middlePosition : '', | ||
}); | ||
applyArrowStyles(arrowEl, arrowRotation(actualDirection), actualDirection); | ||
} | ||
export async function useRecompute(state) { | ||
if (!state.isShowing) | ||
if (!state?.isShowing) | ||
return; // we're not currently showing the element, no reason to recompute | ||
await state?.waitForDOM?.(); // wait for DOM to settle before computing | ||
if (state.isCallout) | ||
return computeCalloutArrow(state); // we don't move the callout box, only its arrow | ||
const position = await computePosition(state.targetEl, state.attentionEl, { | ||
placement: state.directionName, | ||
if (state?.waitForDOM) { | ||
await state?.waitForDOM(); // wait for DOM to settle before computing | ||
} | ||
if (state?.isCallout) | ||
return computeCalloutArrow(state); | ||
if (!state?.targetEl || !state?.attentionEl) | ||
return; | ||
const targetEl = state?.targetEl; | ||
const attentionEl = state?.attentionEl; | ||
computePosition(targetEl, attentionEl, { | ||
placement: state?.directionName ?? BOTTOM, | ||
middleware: [ | ||
// Should we make this configurable, but have these as sane defaults? | ||
flip(), | ||
offset(8), | ||
offset({ mainAxis: state?.distance ?? 8, crossAxis: state?.skidding ?? 0 }), | ||
state?.flip && flip({ | ||
fallbackAxisSideDirection: 'start', | ||
fallbackPlacements: state?.fallbackPlacements, | ||
}), | ||
!state?.flip && autoPlacement(), | ||
shift({ padding: 16 }), | ||
// @ts-ignore | ||
arrow({ element: state.noArrow ? undefined : state.arrowEl }), // FIXME | ||
!state?.noArrow && state?.arrowEl && arrow({ element: state?.arrowEl }), | ||
], | ||
}).then(({ x, y, middlewareData, placement }) => { | ||
state.actualDirection = placement; | ||
Object.assign(attentionEl?.style, { | ||
left: `${x}px`, | ||
top: `${y}px`, | ||
}); | ||
const isRtl = window.getComputedStyle(attentionEl).direction === 'rtl'; //checks whether the text direction of the attentionEl is right-to-left. Helps to calculate the position of the arrowEl and ensure proper alignment | ||
const arrowPlacement = arrowDirection(placement).split('-')[1]; | ||
if (middlewareData?.arrow && state?.arrowEl) { | ||
const arrowEl = state?.arrowEl; | ||
const { x, y } = middlewareData?.arrow; | ||
let top = ''; | ||
let right = ''; | ||
let bottom = ''; | ||
let left = ''; | ||
// calculates the arrow-position depending on if placement has 'start' or 'end': | ||
if (arrowPlacement === 'start') { | ||
const value = typeof x === 'number' | ||
? `calc(33px - ${arrowEl?.offsetWidth / 2}px)` | ||
: ''; | ||
top = | ||
typeof y === 'number' | ||
? `calc(33px - ${arrowEl?.offsetWidth / 2}px)` | ||
: ''; | ||
right = isRtl ? value : ''; | ||
left = isRtl ? '' : value; | ||
} | ||
else if (arrowPlacement === 'end') { | ||
const value = typeof x === 'number' | ||
? `calc(33px - ${arrowEl?.offsetWidth / 2}px)` | ||
: ''; | ||
right = isRtl ? '' : value; | ||
left = isRtl ? value : ''; | ||
bottom = | ||
typeof y === 'number' | ||
? `calc(33px - ${arrowEl?.offsetWidth / 2}px)` | ||
: ''; | ||
} | ||
else { | ||
left = typeof x === 'number' ? `${x}px` : ''; | ||
top = typeof y === 'number' ? `${y}px` : ''; | ||
} | ||
Object.assign(arrowEl?.style || {}, { | ||
top, | ||
right, | ||
bottom, | ||
left, | ||
}); | ||
applyArrowStyles(arrowEl, arrowRotation(placement), placement); | ||
} | ||
}); | ||
// @ts-ignore | ||
state.actualDirection = position.placement; | ||
Object.assign(state.attentionEl?.style || {}, { | ||
left: "0", | ||
top: "0", | ||
transform: `translate3d(${Math.round(position.x)}px, ${Math.round(position.y)}px, 0)`, | ||
return state; | ||
} | ||
export const autoUpdatePosition = (state) => { | ||
// computePosition is only run once, so we need to wrap autoUpdate() around useRecompute() in order to recompute the attentionEl's position | ||
// autoUpdate adds event listeners that are triggered on resize and on scroll and will keep calling the useRecompute(). | ||
// autoUpdate returns a cleanup() function that removes the event listeners. | ||
if (!state?.targetEl || !state?.attentionEl) | ||
return; | ||
return autoUpdate(state?.targetEl, state?.attentionEl, () => { | ||
useRecompute(state); | ||
}); | ||
// @ts-ignore | ||
let { x, y } = position.middlewareData.arrow; | ||
if (state.arrowEl) { | ||
state.arrowEl.style.left = x ? x + "px" : ""; | ||
state.arrowEl.style.top = y ? y + "px" : ""; | ||
} | ||
} | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"description": "Shared business logic for JS implementations of Warp Design System", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"type": "module", | ||
@@ -27,2 +27,5 @@ "exports": { | ||
], | ||
"peerDependencies": { | ||
"@floating-ui/dom": "1.6.3" | ||
}, | ||
"scripts": { | ||
@@ -36,5 +39,2 @@ "commit": "cz", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"@floating-ui/dom": "^0.5.0" | ||
}, | ||
"devDependencies": { | ||
@@ -41,0 +41,0 @@ "typescript": "^4.7.4", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30224
431
+ Added@floating-ui/core@1.6.8(transitive)
+ Added@floating-ui/dom@1.6.3(transitive)
+ Added@floating-ui/utils@0.2.8(transitive)
- Removed@floating-ui/dom@^0.5.0
- Removed@floating-ui/core@0.7.3(transitive)
- Removed@floating-ui/dom@0.5.4(transitive)