-
Notifications
You must be signed in to change notification settings - Fork 216
feat: adds joystick3d with modifier keys #582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 5ebdd53 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@gsimone here's an updated version for 1.0. Check it out in the storybook:
|
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 5ebdd53:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds 3D joystick support to Vector3d inputs, allowing users to manipulate 3D vectors through an interactive joystick interface with plane switching capabilities. The implementation reuses the existing 2D joystick component and extends it to support three planes (XY, XZ, ZY) that can be switched using keyboard modifiers.
Key changes:
- Extracted and refactored the Joystick component from Vector2d to be reusable across both Vector2d and Vector3d
- Added a new
Joystick3dcomponent that wraps the base Joystick with plane-switching controls - Introduced a
useKeyPresshook for tracking keyboard modifier states - Added visual feedback via a 3D cube (JoyCube) showing the current active plane
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/leva/stories/inputs/Vector.stories.tsx | Added Storybook stories demonstrating Vector3d joystick functionality with various configurations |
| packages/leva/src/plugins/Vector3d/vector3d-types.ts | Extended type definition to include joystick configuration option |
| packages/leva/src/plugins/Vector3d/index.ts | Added normalize function to handle joystick settings during plugin initialization |
| packages/leva/src/plugins/Vector3d/Vector3d.tsx | Integrated Joystick3d component into Vector3d rendering logic |
| packages/leva/src/plugins/Vector2d/Vector2d.tsx | Updated to import Joystick from shared UI components location |
| packages/leva/src/hooks/useKeyPress.ts | New hook for detecting and tracking keyboard key press states |
| packages/leva/src/hooks/index.ts | Exported the new useKeyPress hook |
| packages/leva/src/components/UI/index.ts | Exported new UI components (Joystick, JoyCube, Joystick3d) |
| packages/leva/src/components/UI/StyledJoystick3d.ts | Styled components for 3D joystick UI elements including cube faces and button controls |
| packages/leva/src/components/UI/StyledJoystick.ts | Extracted shared styled components from Vector2d for reuse |
| packages/leva/src/components/UI/Joystick3d.tsx | New component managing 3D joystick with plane selection and keyboard controls |
| packages/leva/src/components/UI/Joystick.tsx | Refactored joystick component to support both 2D and 3D vectors |
| packages/leva/src/components/UI/JoyCube.tsx | 3D cube visualization component for showing active plane |
| packages/leva/src/components/Button/Button.tsx | Extended label prop type to accept JSX.Element for custom button content |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds 2D and 3D joystick UI (Joystick, Joystick3d, JoyCube) with styling and a useKeyPress hook, integrates joystick support into Vector3d (normalize wrapper and new joystick setting), expands Button.label to accept JSX.Element, updates imports/exports, and adds stories demonstrating joystick usage. Changes
Sequence DiagramsequenceDiagram
participant User
participant Joystick3d
participant useKeyPress
participant JoyCube
participant Joystick
participant Vector3dPlugin
User->>Joystick3d: drag + modifier keys
Joystick3d->>useKeyPress: subscribe to modifier keys
useKeyPress-->>Joystick3d: key pressed state
Joystick3d->>JoyCube: update orientation visuals
Joystick3d->>Joystick: provide mapped 2D settings for active plane
Joystick->>Joystick: handle drag, clamp, step multiplier, out-of-bounds loop
Joystick->>Vector3dPlugin: onUpdate(new vector)
Vector3dPlugin-->>User: re-rendered vector value
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas to focus on:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
packages/leva/src/components/UI/StyledJoystick.ts (1)
46-53: Fix thezIndextypo.
zindexisn’t a valid CSS-in-JS key, so the handle/grid layering never gets the intended stacking order. Please change both occurrences tozIndex.- zindex: 100, + zIndex: 100, … - zindex: 10, + zIndex: 10,Also applies to: 66-70
packages/leva/src/components/UI/Joystick3d.tsx (1)
54-56: Use a no-op callback instead of returning a string.
onClick={() => ''}is a leftover stub; keep it as a proper no-op to avoid pointless string returns.- onClick={() => ''} + onClick={() => {}}
🧹 Nitpick comments (1)
packages/leva/src/plugins/Vector3d/Vector3d.tsx (1)
6-6: Consider extracting Container to a shared location.Importing
ContainerfromVector2dcreates an inter-plugin dependency, which is unconventional. While this works pragmatically, consider extractingContainerto a shared UI module (e.g.,components/UI/Container.tsx) for better separation of concerns.For now, the implementation mirrors Vector2d correctly and functions as intended.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
packages/leva/src/components/Button/Button.tsx(1 hunks)packages/leva/src/components/UI/JoyCube.tsx(1 hunks)packages/leva/src/components/UI/Joystick.tsx(1 hunks)packages/leva/src/components/UI/Joystick3d.tsx(1 hunks)packages/leva/src/components/UI/StyledJoystick.ts(1 hunks)packages/leva/src/components/UI/StyledJoystick3d.ts(1 hunks)packages/leva/src/components/UI/index.ts(1 hunks)packages/leva/src/hooks/index.ts(1 hunks)packages/leva/src/hooks/useKeyPress.ts(1 hunks)packages/leva/src/plugins/Vector2d/Vector2d.tsx(1 hunks)packages/leva/src/plugins/Vector3d/Vector3d.tsx(1 hunks)packages/leva/src/plugins/Vector3d/index.ts(1 hunks)packages/leva/src/plugins/Vector3d/vector3d-types.ts(1 hunks)packages/leva/stories/inputs/Vector.stories.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
packages/leva/src/components/Button/Button.tsx (1)
packages/leva/src/components/UI/Label.tsx (4)
Label(53-94)RawLabel(24-51)OptionalToggle(7-20)disable(15-15)
packages/leva/src/components/UI/StyledJoystick.ts (1)
packages/leva/src/styles/stitches.config.ts (3)
touchAction(133-137)display(118-122)display(114-117)
packages/leva/src/plugins/Vector3d/vector3d-types.ts (1)
packages/leva/src/plugins/Vector/vector-types.ts (1)
InternalVectorSettings(55-57)
packages/leva/src/components/UI/Joystick.tsx (4)
packages/leva/src/plugins/Vector2d/vector2d-types.ts (1)
Vector2dProps(7-7)packages/leva/src/utils/event.ts (1)
multiplyStep(1-1)packages/leva/src/components/UI/StyledJoystick.ts (3)
JoystickTrigger(3-24)JoystickPlayground(26-54)JoystickGrid(56-81)packages/leva/src/components/UI/Misc.tsx (1)
Portal(7-14)
packages/leva/src/plugins/Vector3d/index.ts (5)
packages/leva/src/plugins/Vector/vector-plugin.ts (1)
getVectorPlugin(166-175)packages/leva/src/plugins/Vector3d/vector3d-types.ts (1)
InternalVector3dSettings(4-6)packages/leva/src/plugin.ts (3)
createInternalPlugin(49-53)normalize(75-88)format(103-107)packages/leva/src/plugins/Vector3d/Vector3d.tsx (1)
Vector3dComponent(8-19)packages/leva/src/types/internal.ts (1)
InternalPlugin(95-98)
packages/leva/src/components/UI/Joystick3d.tsx (7)
packages/leva/src/plugins/Vector3d/vector3d-types.ts (1)
Vector3dProps(7-7)packages/leva/src/hooks/useKeyPress.ts (1)
useKeyPress(9-36)packages/leva/src/plugins/Vector2d/vector2d-types.ts (1)
InternalVector2dSettings(4-6)packages/leva/src/components/UI/Joystick.tsx (1)
Joystick(15-141)packages/leva/src/components/UI/JoyCube.tsx (1)
JoyCube(12-53)packages/leva/src/components/UI/StyledJoystick3d.ts (4)
JoystickButtons(4-18)ButtonLabelContainer(20-27)KeyLabel(36-44)PlaneLabel(29-34)packages/leva/src/components/Button/Button.tsx (1)
Button(11-20)
packages/leva/src/components/UI/StyledJoystick3d.ts (1)
packages/leva/src/components/UI/StyledJoystick.ts (1)
JoystickGrid(56-81)
packages/leva/src/components/UI/JoyCube.tsx (1)
packages/leva/src/components/UI/StyledJoystick3d.ts (2)
StyledJoyCube(52-157)StyledJoyCubeFace(46-50)
packages/leva/src/hooks/useKeyPress.ts (1)
packages/leva/src/components/Leva/Filter.tsx (4)
handleShortcut(99-107)ref(10-48)event(100-104)window(106-106)
packages/leva/src/plugins/Vector2d/Vector2d.tsx (1)
packages/plugin-spring/src/Spring.tsx (1)
Spring(8-22)
packages/leva/src/plugins/Vector3d/Vector3d.tsx (3)
packages/leva/src/plugins/Vector2d/Vector2d.tsx (1)
Container(8-17)packages/leva/src/plugins/Vector/Vector.tsx (2)
Container(45-58)Vector(84-111)packages/leva/src/components/UI/Joystick3d.tsx (1)
Joystick3d(24-62)
🔇 Additional comments (8)
packages/leva/src/components/Button/Button.tsx (1)
8-8: LGTM! Clean API extension.Broadening the label type to accept
JSX.Elementenables richer button content (e.g., icons, styled text) while maintaining backward compatibility. The implementation correctly handles both types.packages/leva/src/hooks/index.ts (1)
23-23: LGTM!The export is properly placed under the UI/Animation Utilities section and follows the existing pattern.
packages/leva/src/components/UI/index.ts (1)
5-7: LGTM!The new UI component exports follow the established barrel export pattern and properly expose the 3D joystick components to consumers.
packages/leva/src/plugins/Vector2d/Vector2d.tsx (1)
4-4: LGTM! Clean refactor.Consolidating the import to use the UI barrel improves code organization and aligns with the centralized UI component pattern.
packages/leva/src/plugins/Vector3d/Vector3d.tsx (1)
13-16: LGTM! Consistent integration pattern.The conditional rendering of
Joystick3dandContainerwrapper correctly mirrors the Vector2d implementation, providing a consistent API across both plugins.packages/leva/src/plugins/Vector3d/vector3d-types.ts (1)
4-6: LGTM! Clear type definition.The
joystickproperty type (boolean | 'invertY') is concise and expressive, allowing both simple enablement and axis inversion configuration.packages/leva/src/components/UI/JoyCube.tsx (1)
12-53: LGTM! Explicit structure aids clarity.The component correctly renders up to three depth layers of cube faces, with each layer containing all six faces. While verbose, the explicit structure makes the CSS targeting straightforward and the intent clear.
packages/leva/src/components/UI/StyledJoystick3d.ts (1)
52-157: Well-structured 3D CSS implementation.The StyledJoyCube component correctly implements a 3D cube visualization using CSS transforms and custom properties. The approach is solid:
- CSS custom properties provide maintainable size calculations
preserve-3dandbackfaceVisibilityare properly used- Face selectors with depth layers (primary/mid/rear) create effective 3D perspective
- Smooth transitions between orientations
The 18 face selectors are somewhat repetitive, but this explicit approach is clear and appropriate for CSS-in-JS declarations.
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/leva/src/components/UI/Joystick.tsx (1)
53-74: Consider caching axis indices to avoid repeated lookups.The
AXIS_KEYS.indexOf(v1)andAXIS_KEYS.indexOf(v2)calls (lines 65-66) execute on every 16ms interval tick. Sincev1andv2are stable axis keys from settings, you could compute these indices once and reuse them.For example, compute the indices outside the interval:
const startOutOfBounds = useCallback(() => { if (timeout.current) return setIsOutOfBounds(true) if (outOfBoundsX.current) set({ x: outOfBoundsX.current * w }) if (outOfBoundsY.current) set({ y: outOfBoundsY.current * -h }) + const v1Index = AXIS_KEYS.indexOf(v1) + const v2Index = AXIS_KEYS.indexOf(v2) timeout.current = window.setInterval(() => { onUpdate((v: Vector2d | Vector3d) => { const incX = stepV1 * outOfBoundsX.current * stepMultiplier.current const incY = yFactor * stepV2 * outOfBoundsY.current * stepMultiplier.current return Array.isArray(v) ? { - [v1]: v[AXIS_KEYS.indexOf(v1)] + incX, - [v2]: v[AXIS_KEYS.indexOf(v2)] + incY, + [v1]: v[v1Index] + incX, + [v2]: v[v2Index] + incY, } : { [v1]: v[v1] + incX, [v2]: v[v2] + incY, } }) }, 16) }, [w, h, onUpdate, set, stepV1, stepV2, v1, v2, yFactor])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/leva/src/components/UI/Joystick.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/leva/src/components/UI/Joystick.tsx (5)
packages/leva/src/plugins/Vector2d/vector2d-types.ts (1)
Vector2dProps(7-7)packages/leva/src/utils/event.ts (1)
multiplyStep(1-1)packages/leva/src/components/UI/StyledJoystick.ts (3)
JoystickTrigger(3-24)JoystickPlayground(26-54)JoystickGrid(56-81)packages/leva/src/components/UI/Misc.tsx (1)
Portal(7-14)packages/leva/src/plugins/Vector2d/Joystick.tsx (5)
Joystick(13-138)onUpdate(54-68)v(55-67)timeout(49-69)joystickShown(27-33)
🔇 Additional comments (5)
packages/leva/src/components/UI/Joystick.tsx (5)
1-15: LGTM on imports and AXIS_KEYS constant.The imports are well-organized, and extracting
AXIS_KEYSas a module-level constant avoids repeated allocations in the interval callback.
31-37: LGTM on positioning logic.The layout effect correctly positions the playground relative to the trigger. The non-null assertions are safe since the refs are guaranteed to be populated when
joystickShownis true.
82-93: LGTM on keyboard event handling.The global keyboard listeners allow modifier keys (Shift/Alt) to adjust the step multiplier during drag, and the cleanup properly prevents memory leaks.
95-129: LGTM on drag handler logic.The drag handler correctly manages:
- Joystick visibility
- Movement clamping and out-of-bounds detection
- Incremental value updates with modifier keys
- Y-axis inversion via
yFactorThe
@ts-expect-errorsuppressions (lines 104, 106) are unfortunate but necessary given thatvaluecan be either an array or object, and the axis keys are dynamic strings from settings.
131-142: LGTM on render structure.The composition pattern using the optional
childrenprop allows Joystick3d to inject custom indicators (like JoyCube), while defaulting to JoystickGrid for 2D use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
packages/leva/src/components/UI/Joystick.tsx (4)
31-37: Consider optional chaining for safer ref access.While the
joystickShownguard likely ensures refs are set, the non-null assertions bypass TypeScript's safety checks. Optional chaining would be more defensive.useLayoutEffect(() => { if (joystickShown) { - const { top, left, width, height } = joystickRef.current!.getBoundingClientRect() - playgroundRef.current!.style.left = left + width / 2 + 'px' - playgroundRef.current!.style.top = top + height / 2 + 'px' + const rect = joystickRef.current?.getBoundingClientRect() + const playground = playgroundRef.current + if (rect && playground) { + playground.style.left = rect.left + rect.width / 2 + 'px' + playground.style.top = rect.top + rect.height / 2 + 'px' + } } }, [joystickShown])
53-74: Consider optimizing axis index lookups in the hot path.Lines 65-66 call
AXIS_KEYS.indexOf()inside the interval callback, which fires every 16ms when out of bounds. While the array is small (3 elements), this could be optimized with a lookup object computed once.Create a lookup object outside the component:
const AXIS_KEYS = ['x', 'y', 'z'] as const const AXIS_INDEX: Record<string, number> = { x: 0, y: 1, z: 2 }Then replace the indexOf calls:
return Array.isArray(v) ? { - [v1]: v[AXIS_KEYS.indexOf(v1 as 'x' | 'y' | 'z')] + incX, - [v2]: v[AXIS_KEYS.indexOf(v2 as 'x' | 'y' | 'z')] + incY, + [v1]: v[AXIS_INDEX[v1]] + incX, + [v2]: v[AXIS_INDEX[v2]] + incY, } : { [v1]: v[v1] + incX, [v2]: v[v2] + incY, }
63-72: Type detection could be more explicit.Using
Array.isArray(v)to distinguish Vector3d from Vector2d couples the code to implementation details (array vs object representation). Consider using a more explicit type guard or checking for a discriminant property.Example approach:
// In the types file, add a type guard function isVector3dArray(v: Vector2d | Vector3d): v is [number, number, number] { return Array.isArray(v) }Or check for specific properties:
return v1 in v && v2 in v ? { [v1]: v[v1] + incX, [v2]: v[v2] + incY, } : { [v1]: v[AXIS_INDEX[v1]] + incX, [v2]: v[AXIS_INDEX[v2]] + incY, }
136-136: Simplify the children fallback.The ternary can be replaced with a more concise logical OR operator.
- {children ? children : <JoystickGrid />} + {children || <JoystickGrid />}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/leva/src/components/UI/Joystick.tsx(1 hunks)packages/leva/src/components/UI/StyledJoystick3d.ts(1 hunks)packages/leva/src/hooks/useKeyPress.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/leva/src/hooks/useKeyPress.ts
- packages/leva/src/components/UI/StyledJoystick3d.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/leva/src/components/UI/Joystick.tsx (4)
packages/leva/src/plugins/Vector2d/vector2d-types.ts (1)
Vector2dProps(7-7)packages/leva/src/utils/event.ts (1)
multiplyStep(1-1)packages/leva/src/components/UI/StyledJoystick.ts (3)
JoystickTrigger(3-24)JoystickPlayground(26-54)JoystickGrid(56-81)packages/leva/src/components/UI/Misc.tsx (1)
Portal(7-14)
🔇 Additional comments (4)
packages/leva/src/components/UI/Joystick.tsx (4)
1-15: LGTM!The imports, type definitions, and AXIS_KEYS constant are well-structured. Extracting AXIS_KEYS as a module-level constant avoids repeated allocations during render cycles.
17-29: LGTM!The refs and state initialization follow React best practices. Using refs for timeout and multiplier values that don't need to trigger re-renders is the right choice.
39-51: LGTM!Settings extraction and bounds calculation are clean and correct. The 0.8 factor aligns with the JoystickGrid styling (80% width/height).
82-93: LGTM!The keyboard effect properly sets up global listeners for step multiplier adjustments. Cleanup is thorough, preventing memory leaks by removing listeners and clearing the interval.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/leva/src/components/UI/Joystick.tsx (1)
77-85: Guard array axis lookup during out-of-bounds updatesWhen
vis an array, hitting a plane whose key we don’t recognise (or a future axis rename) makesAXIS_KEYS.indexOf(...)return-1, sov[-1]isundefinedand the interval writesNaN. Since you already addedgetAxisValue, we can reuse it here to keep the array/object paths aligned and robust.- return Array.isArray(v) - ? { - [v1]: v[AXIS_KEYS.indexOf(v1 as 'x' | 'y' | 'z')] + incX, - [v2]: v[AXIS_KEYS.indexOf(v2 as 'x' | 'y' | 'z')] + incY, - } - : { + return Array.isArray(v) + ? { + [v1]: getAxisValue(v, v1) + incX, + [v2]: getAxisValue(v, v2) + incY, + } + : { [v1]: v[v1] + incX, [v2]: v[v2] + incY, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/leva/src/components/UI/Joystick.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/leva/src/components/UI/Joystick.tsx (6)
packages/leva/src/plugins/Vector2d/vector2d-types.ts (1)
Vector2dProps(7-7)packages/leva/src/utils/event.ts (1)
multiplyStep(1-1)packages/leva/src/components/UI/StyledJoystick.ts (3)
JoystickTrigger(3-24)JoystickPlayground(26-54)JoystickGrid(56-81)packages/leva/src/components/UI/Misc.tsx (1)
Portal(7-14)packages/leva/src/plugins/Vector2d/Joystick.tsx (8)
Joystick(13-138)delta(90-124)joystickShown(27-33)onUpdate(54-68)timeout(49-69)v(55-67)window(83-87)window(71-75)packages/leva/src/plugins/Vector2d/Vector2d.tsx (1)
Vector2dComponent(20-31)
|
I went through the code suggestions. Please let me know if there's anything else needed |

Summary
Adds an interactive 3D joystick control for Vector3d inputs with visual feedback and keyboard modifier support for plane switching.
Key Features
JoyCubecomponent renders a 3D cube that rotates to indicate the current editing planeCtrl/^: XZ planeCmd/Win: ZY planeTechnical Changes
New Files
packages/leva/src/components/UI/JoyCube.tsx- 3D cube component with configurable face layerspackages/leva/src/components/UI/Joystick3d.tsx- 3D joystick wrapper with plane switching logicpackages/leva/src/components/UI/StyledJoystick3d.ts- Stitches-based styled components for 3D joystick UIpackages/leva/src/hooks/useKeyPress.ts- Hook for detecting modifier key pressesModified Files
packages/leva/src/components/UI/Joystick.tsx- Addedchildrenprop support for custom visual indicatorspackages/leva/src/components/UI/Misc.tsx- Enhanced Portal component with container proppackages/leva/src/plugins/Vector3d/Vector3d.tsx- Integrated Joystick3d componentCode Quality
Supersedes
This PR supersedes #433 with a significantly enhanced implementation featuring better UX, visual feedback, and cross-platform support.
Summary by CodeRabbit
New Features
UI Enhancements
Docs / Examples