@@ -68,52 +67,28 @@ function useStyleChangeCallback() {
const editor = useEditor()
const trackEvent = useUiEvents()
- return React.useMemo(() => {
- return function handleStyleChange
(style: StyleProp, value: T, squashing: boolean) {
- editor.batch(() => {
- if (editor.isIn('select')) {
- editor.setStyleForSelectedShapes(style, value, { squashing })
- }
- editor.setStyleForNextShapes(style, value, { squashing })
- editor.updateInstanceState({ isChangingStyle: true })
- })
+ return React.useMemo(
+ () =>
+ function handleStyleChange(style: StyleProp, value: T, squashing: boolean) {
+ editor.batch(() => {
+ if (editor.isIn('select')) {
+ editor.setStyleForSelectedShapes(style, value, { squashing })
+ }
+ editor.setStyleForNextShapes(style, value, { squashing })
+ editor.updateInstanceState({ isChangingStyle: true })
+ })
- trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
- }
- }, [editor, trackEvent])
+ trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
+ },
+ [editor, trackEvent]
+ )
}
-const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
-
-function CommonStylePickerSet({
- styles,
- opacity,
-}: {
- styles: ReadonlySharedStyleMap
- opacity: SharedStyle
-}) {
- const editor = useEditor()
- const trackEvent = useUiEvents()
+function CommonStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const msg = useTranslation()
const handleValueChange = useStyleChangeCallback()
- const handleOpacityValueChange = React.useCallback(
- (value: number, ephemeral: boolean) => {
- const item = tldrawSupportedOpacities[value]
- editor.batch(() => {
- if (editor.isIn('select')) {
- editor.setOpacityForSelectedShapes(item, { ephemeral })
- }
- editor.setOpacityForNextShapes(item, { ephemeral })
- editor.updateInstanceState({ isChangingStyle: true })
- })
-
- trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
- },
- [editor, trackEvent]
- )
-
const color = styles.get(DefaultColorStyle)
const fill = styles.get(DefaultFillStyle)
const dash = styles.get(DefaultDashStyle)
@@ -121,15 +96,6 @@ function CommonStylePickerSet({
const showPickers = fill !== undefined || dash !== undefined || size !== undefined
- const opacityIndex =
- opacity.type === 'mixed'
- ? -1
- : tldrawSupportedOpacities.indexOf(
- minBy(tldrawSupportedOpacities, (supportedOpacity) =>
- Math.abs(supportedOpacity - opacity.value)
- )!
- )
-
return (
<>
)}
- {opacity === undefined ? null : (
- = 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
- label={
- opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`
- }
- onValueChange={handleOpacityValueChange}
- steps={tldrawSupportedOpacities.length - 1}
- title={msg('style-panel.opacity')}
- />
- )}
+
{showPickers && (
@@ -331,3 +286,50 @@ function ArrowheadStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap })
/>
)
}
+
+const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
+
+function OpacitySlider() {
+ const editor = useEditor()
+ const opacity = useValue('opacity', () => editor.getSharedOpacity(), [editor])
+ const trackEvent = useUiEvents()
+ const msg = useTranslation()
+
+ const handleOpacityValueChange = React.useCallback(
+ (value: number, ephemeral: boolean) => {
+ const item = tldrawSupportedOpacities[value]
+ editor.batch(() => {
+ if (editor.isIn('select')) {
+ editor.setOpacityForSelectedShapes(item, { ephemeral })
+ }
+ editor.setOpacityForNextShapes(item, { ephemeral })
+ editor.updateInstanceState({ isChangingStyle: true })
+ })
+
+ trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
+ },
+ [editor, trackEvent]
+ )
+
+ if (opacity === undefined) return null
+
+ const opacityIndex =
+ opacity.type === 'mixed'
+ ? -1
+ : tldrawSupportedOpacities.indexOf(
+ minBy(tldrawSupportedOpacities, (supportedOpacity) =>
+ Math.abs(supportedOpacity - opacity.value)
+ )!
+ )
+
+ return (
+
= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
+ label={opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`}
+ onValueChange={handleOpacityValueChange}
+ steps={tldrawSupportedOpacities.length - 1}
+ title={msg('style-panel.opacity')}
+ />
+ )
+}
diff --git a/packages/tldraw/src/lib/ui/components/menu-items.tsx b/packages/tldraw/src/lib/ui/components/menu-items.tsx
index b7fcd8bf7..246ffd3a0 100644
--- a/packages/tldraw/src/lib/ui/components/menu-items.tsx
+++ b/packages/tldraw/src/lib/ui/components/menu-items.tsx
@@ -282,7 +282,7 @@ export function ArrangeMenuSubmenu() {
if (!(twoSelected || onlyFlippableShapeSelected)) return null
return (
-
+
{twoSelected && (
diff --git a/packages/tldraw/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx b/packages/tldraw/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx
index 61694ca2e..93472c498 100644
--- a/packages/tldraw/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx
+++ b/packages/tldraw/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx
@@ -139,6 +139,7 @@ export type TLUiDropdownMenuSubContentProps = {
id?: string
alignOffset?: number
sideOffset?: number
+ size?: 'tiny' | 'small' | 'medium' | 'wide'
children: any
}
@@ -146,6 +147,7 @@ export type TLUiDropdownMenuSubContentProps = {
export function TldrawUiDropdownMenuSubContent({
alignOffset = -1,
sideOffset = -4,
+ size = 'small',
children,
}: TLUiDropdownMenuSubContentProps) {
const container = useContainer()
@@ -156,6 +158,7 @@ export function TldrawUiDropdownMenuSubContent({
alignOffset={alignOffset}
sideOffset={sideOffset}
collisionPadding={4}
+ data-size={size}
>
{children}
@@ -166,16 +169,12 @@ export function TldrawUiDropdownMenuSubContent({
/** @public */
export type TLUiDropdownMenuGroupProps = {
children: any
- size?: 'tiny' | 'small' | 'medium' | 'wide'
}
/** @public */
-export function TldrawUiDropdownMenuGroup({
- children,
- size = 'medium',
-}: TLUiDropdownMenuGroupProps) {
+export function TldrawUiDropdownMenuGroup({ children }: TLUiDropdownMenuGroupProps) {
return (
- <_DropdownMenu.Group dir="ltr" className="tlui-menu__group" data-size={size}>
+ <_DropdownMenu.Group dir="ltr" className="tlui-menu__group">
{children}
)
diff --git a/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx b/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx
index 1b9533959..56e9db524 100644
--- a/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx
+++ b/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx
@@ -12,12 +12,11 @@ export type TLUiMenuGroupProps = {
* The label to display on the item. If it's a string, it will be translated. If it's an object, the keys will be used as the language keys and the values will be translated.
*/
label?: TranslationKey | { [key: string]: TranslationKey }
- small?: boolean
children?: any
}
/** @public */
-export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMenuGroupProps) {
+export function TldrawUiMenuGroup({ id, label, children }: TLUiMenuGroupProps) {
const { type: menuType, sourceId } = useTldrawUiMenuContext()
const msg = useTranslation()
const labelToUse = unwrapLabel(label, menuType)
@@ -33,10 +32,7 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
}
case 'menu': {
return (
-
+
{children}
)
@@ -46,7 +42,6 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
{children}
diff --git a/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx b/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx
index 7d8ea635e..5a677b8c8 100644
--- a/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx
+++ b/packages/tldraw/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx
@@ -24,7 +24,7 @@ export type TLUiMenuSubmenuProps = {
label?: Translation | { [key: string]: Translation }
disabled?: boolean
children: any
- size?: 'tiny' | 'small' | 'medium' | 'large'
+ size?: 'tiny' | 'small' | 'medium' | 'wide'
}
/** @public */
@@ -32,7 +32,7 @@ export function TldrawUiMenuSubmenu({
id,
disabled = false,
label,
- size,
+ size = 'small',
children,
}: TLUiMenuSubmenuProps) {
const { type: menuType, sourceId } = useTldrawUiMenuContext()
@@ -55,7 +55,7 @@ export function TldrawUiMenuSubmenu({
label={labelStr!}
title={labelStr!}
/>
-
+
{children}
diff --git a/packages/tldraw/src/lib/ui/hooks/useRevelantStyles.ts b/packages/tldraw/src/lib/ui/hooks/useRevelantStyles.ts
index 4e5a8116b..cac7a0211 100644
--- a/packages/tldraw/src/lib/ui/hooks/useRevelantStyles.ts
+++ b/packages/tldraw/src/lib/ui/hooks/useRevelantStyles.ts
@@ -4,18 +4,20 @@ import {
DefaultFillStyle,
DefaultSizeStyle,
ReadonlySharedStyleMap,
- SharedStyle,
SharedStyleMap,
useEditor,
useValue,
} from '@tldraw/editor'
-const selectToolStyles = [DefaultColorStyle, DefaultDashStyle, DefaultFillStyle, DefaultSizeStyle]
+const selectToolStyles = Object.freeze([
+ DefaultColorStyle,
+ DefaultDashStyle,
+ DefaultFillStyle,
+ DefaultSizeStyle,
+])
-export function useRelevantStyles(): {
- styles: ReadonlySharedStyleMap
- opacity: SharedStyle
-} | null {
+/** @public */
+export function useRelevantStyles(stylesToCheck = selectToolStyles): ReadonlySharedStyleMap | null {
const editor = useEditor()
return useValue(
'getRelevantStyles',
@@ -25,13 +27,13 @@ export function useRelevantStyles(): {
editor.getSelectedShapeIds().length > 0 || !!editor.root.getCurrent()?.shapeType
if (styles.size === 0 && editor.isIn('select') && editor.getSelectedShapeIds().length === 0) {
- for (const style of selectToolStyles) {
+ for (const style of stylesToCheck) {
styles.applyValue(style, editor.getStyleForNextShape(style))
}
}
if (styles.size === 0 && !hasShape) return null
- return { styles, opacity: editor.getSharedOpacity() }
+ return styles
},
[editor]
)
diff --git a/packages/tldraw/src/test/TldrawEditor.test.tsx b/packages/tldraw/src/test/TldrawEditor.test.tsx
index a6a550285..2e8d1aca9 100644
--- a/packages/tldraw/src/test/TldrawEditor.test.tsx
+++ b/packages/tldraw/src/test/TldrawEditor.test.tsx
@@ -2,7 +2,6 @@ import { act, render, screen } from '@testing-library/react'
import {
BaseBoxShapeTool,
BaseBoxShapeUtil,
- Canvas,
Editor,
HTMLContainer,
TLBaseShape,
@@ -26,13 +25,10 @@ function checkAllShapes(editor: Editor, shapes: string[]) {
describe('', () => {
it('Renders without crashing', async () => {
await renderTldrawComponent(
-
-
-
- ,
+ ,
{ waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
+ await screen.findByTestId('canvas')
})
it('Creates its own store with core shapes', async () => {
@@ -45,12 +41,9 @@ describe('', () => {
initialState="select"
tools={defaultTools}
autoFocus
- >
-
- ,
+ />,
{ waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
checkAllShapes(editor!, ['group'])
})
@@ -65,13 +58,9 @@ describe('', () => {
editor = e
}}
autoFocus
- >
-
-
- ,
+ />,
{ waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
expect(editor!).toBeTruthy()
checkAllShapes(editor!, ['group'])
@@ -88,13 +77,9 @@ describe('', () => {
expect(editor.store).toBe(store)
}}
autoFocus
- >
-
-
- ,
+ />,
{ waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
})
it('throws if the store has different shapes to the ones passed in', async () => {
@@ -148,11 +133,8 @@ describe('', () => {
store={initialStore}
onMount={onMount}
autoFocus
- >
-
-
+ />
)
- await screen.findByTestId('canvas-1')
const initialEditor = onMount.mock.lastCall[0]
jest.spyOn(initialEditor, 'dispose')
expect(initialEditor.store).toBe(initialStore)
@@ -164,11 +146,8 @@ describe('', () => {
store={initialStore}
onMount={onMount}
autoFocus
- >
-
-
+ />
)
- await screen.findByTestId('canvas-2')
// not called again:
expect(onMount).toHaveBeenCalledTimes(1)
// re-render with a new store:
@@ -180,11 +159,8 @@ describe('', () => {
store={newStore}
onMount={onMount}
autoFocus
- >
-
-
+ />
)
- await screen.findByTestId('canvas-3')
expect(initialEditor.dispose).toHaveBeenCalledTimes(1)
expect(onMount).toHaveBeenCalledTimes(2)
expect(onMount.mock.lastCall[0].store).toBe(newStore)
@@ -201,12 +177,9 @@ describe('', () => {
onMount={(editorApp) => {
editor = editorApp
}}
- >
-
-
-
+ />,
+ { waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
expect(editor).toBeTruthy()
await act(async () => {
@@ -325,13 +298,9 @@ describe('Custom shapes', () => {
onMount={(editorApp) => {
editor = editorApp
}}
- >
-
-
- ,
+ />,
{ waitForPatterns: false }
)
- await screen.findByTestId('canvas-1')
expect(editor).toBeTruthy()
await act(async () => {
diff --git a/packages/tldraw/src/test/commands/lockShapes.test.ts b/packages/tldraw/src/test/commands/lockShapes.test.ts
index 9bc7c655c..c285de573 100644
--- a/packages/tldraw/src/test/commands/lockShapes.test.ts
+++ b/packages/tldraw/src/test/commands/lockShapes.test.ts
@@ -165,12 +165,10 @@ describe('Locked shapes', () => {
describe('Unlocking', () => {
it('Can unlock shapes', () => {
editor.setSelectedShapes([ids.lockedShapeA, ids.lockedShapeB])
- let lockedStatus = [ids.lockedShapeA, ids.lockedShapeB].map(
- (id) => editor.getShape(id)!.isLocked
- )
- expect(lockedStatus).toStrictEqual([true, true])
+ const getLockedStatus = () =>
+ [ids.lockedShapeA, ids.lockedShapeB].map((id) => editor.getShape(id)!.isLocked)
+ expect(getLockedStatus()).toStrictEqual([true, true])
editor.toggleLock(editor.getSelectedShapeIds())
- lockedStatus = [ids.lockedShapeA, ids.lockedShapeB].map((id) => editor.getShape(id)!.isLocked)
- expect(lockedStatus).toStrictEqual([false, false])
+ expect(getLockedStatus()).toStrictEqual([false, false])
})
})
diff --git a/packages/tldraw/src/test/testutils/renderTldrawComponent.tsx b/packages/tldraw/src/test/testutils/renderTldrawComponent.tsx
index 3a8fb060b..5cf9ccc69 100644
--- a/packages/tldraw/src/test/testutils/renderTldrawComponent.tsx
+++ b/packages/tldraw/src/test/testutils/renderTldrawComponent.tsx
@@ -15,7 +15,7 @@ import { ReactElement } from 'react'
*/
export async function renderTldrawComponent(
element: ReactElement,
- { waitForPatterns = true } = {}
+ { waitForPatterns }: { waitForPatterns: boolean }
) {
const result = render(element)
if (waitForPatterns) await result.findByTestId('ready-pattern-fill-defs')
@@ -24,7 +24,7 @@ export async function renderTldrawComponent(
export async function renderTldrawComponentWithEditor(
cb: (onMount: (editor: Editor) => void) => ReactElement,
- opts?: { waitForPatterns?: boolean }
+ opts: { waitForPatterns: boolean }
) {
const editorPromise = promiseWithResolve()
const element = cb((editor) => {
diff --git a/packages/tldraw/src/test/ui/ContextMenu.test.tsx b/packages/tldraw/src/test/ui/ContextMenu.test.tsx
index 9c790367c..6ef49e17f 100644
--- a/packages/tldraw/src/test/ui/ContextMenu.test.tsx
+++ b/packages/tldraw/src/test/ui/ContextMenu.test.tsx
@@ -10,7 +10,8 @@ it('opens on right-click', async () => {
onMount={(editor) => {
editor.createShape({ id: createShapeId(), type: 'geo' })
}}
- />
+ />,
+ { waitForPatterns: false }
)
const canvas = await screen.findByTestId('canvas')
@@ -38,7 +39,8 @@ it('tunnels context menu', async () => {
editor.createShape({ id: createShapeId(), type: 'geo' })
}}
components={components}
- />
+ />,
+ { waitForPatterns: false }
)
const canvas = await screen.findByTestId('canvas')