From 403267a27759f2bb9ad74c57c365ced8eba02f19 Mon Sep 17 00:00:00 2001 From: Simon Reinisch Date: Sun, 19 Jan 2025 16:54:33 +0100 Subject: [PATCH] docs: migrate remaining documentation --- docs/.vitepress/config.mts | 1 + docs/index.md | 12 +- docs/pages/api-reference.md | 269 ++++++++++++++++++-------- docs/pages/custom-integration.md | 97 +++++++++- docs/pages/faq.md | 109 ++++++++++- docs/pages/frameworks/preact.md | 135 ++++++------- docs/pages/frameworks/react.md | 130 ++++++------- docs/pages/frameworks/vanilla.md | 54 +++--- docs/pages/frameworks/vue.md | 63 +++--- docs/pages/quickstart.md | 255 ++++++++++++++++++------ docs/parts/custom-integration-note.md | 3 + 11 files changed, 796 insertions(+), 332 deletions(-) create mode 100644 docs/parts/custom-integration-note.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 87d8438..17390f6 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -23,6 +23,7 @@ export default defineConfig({ {text: 'Quickstart', link: 'pages/quickstart'}, {text: 'API Reference', link: 'pages/api-reference'}, {text: 'Custom Integration', link: 'pages/custom-integration'}, + {text: 'FAQ', link: 'pages/faq'}, ] }, { diff --git a/docs/index.md b/docs/index.md index 9b3b244..ee42f2f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,13 +22,13 @@ features: details: A cutting-edge bundle for modern web development, ensuring compatibility with the latest standards and practices. - icon: πŸ”© title: Ultra Tiny - details: Extremely lightweight, around 4kb in size, making it perfect for performance-critical applications. + details: Extremely lightweight, around 4kb in size, making it perfect for performance and size-critical applications. - icon: πŸ‘Œ title: Simple Usage - details: Easy to use with minimal setup required, allowing developers to integrate it quickly into their projects. + details: Easy to use with minimal setup required, allowing developers to integrate it quickly into any kind of project. - icon: ⚑ title: Highly Optimized - details: Performance-focused and highly efficient, designed to handle large datasets and complex operations smoothly. + details: Performance-focused and highly efficient, designed to handle large amounts of DOM elements with ease. - icon: πŸš€ title: Zero Dependencies details: No external dependencies required, reducing the risk of conflicts and simplifying the build process. @@ -39,10 +39,10 @@ features: title: Scroll Support details: Supports both vertical and horizontal scrolling, ensuring smooth navigation and interaction. - icon: πŸ’ͺ - title: Proven Stability - details: Over 3 years of development and widely used in many applications, demonstrating its reliability and robustness. + title: Battle Tested + details: Over 6 years of development and widely used in many applications, demonstrating its reliability and robustness. - icon: πŸ–Ό title: Framework Support - details: Compatible with major frameworks (work in progress), making it versatile and easy to integrate with various technologies. + details: Compatible with major frameworks and easy to "hack" for custom integrations, providing flexibility and versatility. --- diff --git a/docs/pages/api-reference.md b/docs/pages/api-reference.md index 4dc888a..83c42a7 100644 --- a/docs/pages/api-reference.md +++ b/docs/pages/api-reference.md @@ -7,7 +7,7 @@ outline: deep The `SelectionArea` is the main class of the library, it is responsible for handling the selection process. It is passed to each event and can be used to interact with the selection process. -### Static Properties +## Static Properties The only static property is the version of the library. @@ -15,9 +15,88 @@ The only static property is the version of the library. version: string; ``` -### Methods +## Events -#### `constructor` +All events receive a [selection event](#selectionevent) object. +Events are bind/unbind using the [on](#on--addeventlistener) and [off](#off--removeeventlistener) methods: + +```ts +selection.on('beforestart', evt => { + + // Use this event to decide whether a selection should take place or not. + // For example if the user should be able to normally interact with input-elements you + // may want to prevent a selection if the user clicks such a element: + // selection.on('beforestart', ({event}) => { + // return event.target.tagName !== 'INPUT'; // Returning false prevents a selection + // }); + + console.log('beforestart', evt); +}).on('beforedrag', evt => { + + // Same as 'beforestart' but before a selection via dragging happens. + console.log('beforedrag', evt); +}).on('start', evt => { + + // A selection got initiated, you could now clear the previous selection or + // keep it if in case of multi-selection. + console.log('start', evt); +}).on('move', evt => { + + // Here you can update elements based on their state. + console.log('move', evt); +}).on('stop', evt => { + + // Do something with the selected elements. + console.log('stop', evt); +}); +``` + +### `beforestart` + +The user tapped one of the areas within the specified boundaries. +Return `false` to cancel selection immediately. + +```typescript +beforestart: (e: SelectionEvent) => boolean | void; +``` + +### `beforedrag` + +Same as `beforestart` but _before_ the user starts selecting by dragging the mouse. +Can be used to conditionally allow a selection by dragging. Return `false` to cancel the selection. + +```typescript +beforedrag: (e: SelectionEvent) => boolean | void; +``` + +### `start` + +Selection started, here you can decide if you want to keep your previously selected elements. + +```typescript +start: (e: SelectionEvent) => void; +``` + +### `move` + +Selection is active, user is moving the pointer around. +This is where you apply styles to selected elements and process them further. + +```typescript +move: (e: SelectionEvent) => void; +``` + +### `stop` + +Selection has stopped. + +```typescript +stop: (e: SelectionEvent) => void; +``` + +## Methods + +### `constructor` Instantiates a new `SelectionArea`. @@ -27,7 +106,31 @@ constructor(opt: PartialSelectionOptions): SelectionArea; - `opt: PartialSelectionOptions` - The options for the selection area. -#### `trigger` +### `on` / `addEventListener` + +Binds an event listener, listener functions are documented under [Events](#events). + +```typescript +on(event: string, listener: (e: SelectionEvent) => void): void; +addEventListener(event: string, listener: (e: SelectionEvent) => void): void; +``` + +- `event: string` - The event to listen to. +- `listener: (e: SelectionEvent) => void` - The listener function. + +### `off` / `removeEventListener` + +Unbinds an event listener, listener functions are documented under [Events](#events). + +```typescript +off(event: string, listener: (e: SelectionEvent) => void): void; +removeEventListener(event: string, listener: (e: SelectionEvent) => void): void; +``` + +- `event: string` - The event to unbind. +- `listener: (e: SelectionEvent) => void` - The listener function. + +### `trigger` Manually triggers the start of a selection, can be used to start a selection without a user interaction. @@ -38,7 +141,7 @@ trigger(evt: MouseEvent | TouchEvent, silent = true): void; - `evt: MouseEvent | TouchEvent` - A MouseEvent or TouchEvent-like object. - `silent: boolean` - If `beforestart` should be fired. -#### `resolveSelectables` +### `resolveSelectables` Updates the list of selectables, useful if new elements have been added during a selection. @@ -46,7 +149,7 @@ Updates the list of selectables, useful if new elements have been added during a resolveSelectables(): void; ``` -#### `clearSelection` +### `clearSelection` Clears the selection. @@ -57,7 +160,7 @@ clearSelection(includeStored = true, quiet = false): void; - `includeStored: boolean` - If the store should also get cleared. - `quiet: boolean` - If move/stop events should be fired. -#### `getSelection` +### `getSelection` Returns currently selected elements. @@ -65,7 +168,7 @@ Returns currently selected elements. getSelection(): Element[]; ``` -#### `getSelectionArea` +### `getSelectionArea` Returns the selection area element. @@ -73,7 +176,7 @@ Returns the selection area element. getSelectionArea(): HTMLElement; ``` -#### `getSelectables` +### `getSelectables` Returns all selectables. @@ -81,7 +184,7 @@ Returns all selectables. getSelectables(): Element[]; ``` -#### `setAreaLocation` +### `setAreaLocation` Sets the location of the selection area. @@ -91,7 +194,7 @@ setAreaLocation(location: Partial): void; - `location: Partial` - A partial AreaLocation object. -#### `getAreaLocation` +### `getAreaLocation` Returns the current location of the selection area. @@ -99,7 +202,7 @@ Returns the current location of the selection area. getAreaLocation(): AreaLocation; ``` -#### `cancel` +### `cancel` Cancels the current selection process. @@ -109,7 +212,7 @@ cancel(keepEvent = false): void; - `keepEvent: boolean` - If a stop event should be fired. -#### `destroy` +### `destroy` Unbinds all events and removes the area-element. @@ -117,7 +220,7 @@ Unbinds all events and removes the area-element. destroy(): void; ``` -#### `enable` +### `enable` Enables selecting elements, this is the default state. @@ -125,7 +228,7 @@ Enables selecting elements, this is the default state. enable(): void; ``` -#### `disable` +### `disable` Disables selecting elements. @@ -133,7 +236,7 @@ Disables selecting elements. disable(): void; ``` -#### `select` +### `select` Manually selects elements and adds them to the store. @@ -144,7 +247,7 @@ select(query: SelectAllSelectors, quiet = false): Element[]; - `query: SelectAllSelectors` - CSS Query, can be an array of queries. - `quiet: boolean` - If this should not trigger the move event. -#### `deselect` +### `deselect` Manually deselects elements and removes them from the store. @@ -159,6 +262,9 @@ deselect(query: SelectAllSelectors, quiet = false): Element[]; ### `DeepPartial` +> [!WARNING] +> Internal type, subject to change at any time. + A type that makes all properties in `T` optional and allows for nested optional properties. ```typescript @@ -167,6 +273,9 @@ type DeepPartial = T extends unknown[] ? T : T extends HTMLElement ? T : { [P ### `Quantify` +> [!WARNING] +> Internal type, subject to change at any time. + A type that allows `T` to be an array or a single value. ```typescript @@ -179,8 +288,8 @@ An interface that extends `MouseEvent` with additional properties. ```typescript interface ScrollEvent extends MouseEvent { - deltaY: number; - deltaX: number; + deltaY: number; + deltaX: number; } ``` @@ -190,8 +299,8 @@ An interface representing elements that have been added or removed. ```typescript interface ChangedElements { - added: Element[]; - removed: Element[]; + added: Element[]; + removed: Element[]; } ``` @@ -201,10 +310,10 @@ An interface representing the selection store. ```typescript interface SelectionStore { - touched: Element[]; - stored: Element[]; - selected: Element[]; - changed: ChangedElements; + touched: Element[]; + stored: Element[]; + selected: Element[]; + changed: ChangedElements; } ``` @@ -214,9 +323,9 @@ An interface representing a selection event. ```typescript interface SelectionEvent { - event: MouseEvent | TouchEvent | null; - store: SelectionStore; - selection: SelectionArea; + event: MouseEvent | TouchEvent | null; + store: SelectionStore; + selection: SelectionArea; } ``` @@ -224,36 +333,16 @@ interface SelectionEvent { - `store` - The current state of the selection store. - `selection` - The selection area instance. -### `SelectionEvents` - -An interface representing the selection events. - -```typescript -interface SelectionEvents { - beforestart: (e: SelectionEvent) => boolean | void; - beforedrag: (e: SelectionEvent) => boolean | void; - start: (e: SelectionEvent) => void; - move: (e: SelectionEvent) => void; - stop: (e: SelectionEvent) => void; -} -``` - -- `beforestart` - Fired before the selection starts, if `false` is returned the selection will be canceled. -- `beforedrag` - Fired before the selection area is moved, if `false` is returned the move will be canceled. -- `start` - Fired when the selection starts. -- `move` - Fired when the selection area is moved. -- `stop` - Fired when the selection stops. - ### `AreaLocation` An interface representing the location of the selection area. ```typescript interface AreaLocation { - x1: number; - y1: number; - x2: number; - y2: number; + x1: number; + y1: number; + x2: number; + y2: number; } ``` @@ -263,8 +352,8 @@ An interface representing coordinates. ```typescript interface Coordinates { - x: number; - y: number; + x: number; + y: number; } ``` @@ -279,6 +368,30 @@ type TapMode = 'touch' | 'native'; - `touch` - The element was at the time of click touched "visually" (default). - `native` - The element was the actual element of the click event. +### `Intersection` + +A type representing the intersection mode. + +```typescript +type Intersection = 'center' | 'cover' | 'touch'; +``` + +- `center` - The element is selected if the center of the given element is touched. +- `cover` - The element is selected if the whole element is covered by the selection area. +- `touch` - The element is selected if the selection area touches the element. + +### `Trigger` + +A type representing the trigger for the selection. +Specifies which mouse button or combination of buttons and modifiers should trigger the selection. + +```ts +type MouseButton = 0 | 1 | 2 | 3 | 4; +type Modifier = 'ctrl' | 'alt' | 'shift'; +type MouseButtonWithModifiers = { button: MouseButton; modifiers: Modifier[]; }; +type Trigger = MouseButton | MouseButtonWithModifiers; +``` + ### `OverlapMode` A type representing the overlap mode, e.g. what should happen if you select an element that is already selected. @@ -298,41 +411,41 @@ It consists of the following interfaces: ```typescript interface SingleTap { - allow: boolean; - intersect: TapMode; + allow: boolean; + intersect: TapMode; } interface Features { - deselectOnBlur: boolean; - singleTap: SingleTap; - range: boolean; - touch: boolean; + deselectOnBlur: boolean; + singleTap: SingleTap; + range: boolean; + touch: boolean; } interface Scrolling { - speedDivider: number; - manualSpeed: number; - startScrollMargins: {x: number, y: number}; + speedDivider: number; + manualSpeed: number; + startScrollMargins: {x: number, y: number}; } interface Behaviour { - intersect: Intersection; - startThreshold: number | Coordinates; - overlap: OverlapMode; - scrolling: Scrolling; - triggers: Trigger[]; + intersect: Intersection; + startThreshold: number | Coordinates; + overlap: OverlapMode; + scrolling: Scrolling; + triggers: Trigger[]; } interface SelectionOptions { - selectionAreaClass: string; - selectionContainerClass: string | undefined; - container: Quantify; - document: Document; - selectables: Quantify; - startAreas: Quantify; - boundaries: Quantify; - behaviour: Behaviour; - features: Features; + selectionAreaClass: string; + selectionContainerClass: string | undefined; + container: Quantify; + document: Document; + selectables: Quantify; + startAreas: Quantify; + boundaries: Quantify; + behaviour: Behaviour; + features: Features; } ``` @@ -342,6 +455,6 @@ Type of what can be passed to the `SelectionArea` constructor. ```typescript type PartialSelectionOptions = DeepPartial> & { - document?: Document; + document?: Document; }; ``` diff --git a/docs/pages/custom-integration.md b/docs/pages/custom-integration.md index d63d4ed..4a794d6 100644 --- a/docs/pages/custom-integration.md +++ b/docs/pages/custom-integration.md @@ -6,4 +6,99 @@ This means that you can use it with any framework or library you want. This page should help you to integrate Viselect into your project, no matter if you're using Vue, React, Preact, Angular, or any other framework. Don't worry, there aren't many differences between the integrations, and after you're done you can enjoy all core features of Viselect! -...coming soon! +## Lifecycle + +Generally, viselect is instantiated once and should be re-created once the options need to change. +Don't worry, instantiating and destroying the selection area is a lightweight operation and won't cause any major performance issues. + +Example based on vue and react: + +::: code-group + +```vue [App.vue] + + + +``` + +```tsx [App.tsx] +import React, { useEffect, useRef } from 'react'; +import SelectionArea, { SelectionEvent } from '@viselect/vanilla'; + +export const App = () => { + const container = useRef(null); + const instance = useRef(); + + // Event handlers + const beforeStart = (evt: SelectionEvent) => console.log('beforestart', evt); + const beforeDrag = (evt: SelectionEvent) => console.log('beforedrag', evt); + const start = (evt: SelectionEvent) => console.log('start', evt); + const move = (evt: SelectionEvent) => console.log('move', evt); + const stop = (evt: SelectionEvent) => console.log('stop', evt); + + // Mount the instance and attach events + useEffect(() => { + if (container.current) { + instance.current?.destroy(); + instance.current = new SelectionArea({ + boundaries: container.current, + // ...your options + }); + + // attach events... + instance.current.on('beforestart', beforeStart); + instance.current.on('beforedrag', beforeDrag); + instance.current.on('start', start); + instance.current.on('move', move); + instance.current.on('stop', stop); + } + + return () => instance.current?.destroy(); + }, []); + + return ( +
+ {/* ...elements */} +
+ ); +} +``` + +::: + diff --git a/docs/pages/faq.md b/docs/pages/faq.md index 825a4cf..bcb01bf 100644 --- a/docs/pages/faq.md +++ b/docs/pages/faq.md @@ -2,7 +2,26 @@ outline: deep --- -# Text is selected by default when dragging the mouse over text +# FAQs + +The following are some common questions and answers about `viselect`. + +[[toc]] + +> [!TIP] +> Your question isn't answered here? [Open a discussion](https://github.com/simonwep/viselect/discussions) and ask your question there! +> ...or submit a [PR](https://github.com/simonwep/viselect/compare) or create an [issue](https://github.com/simonwep/viselect/issues/new?assignees=simonwep&labels=&template=feature_request.md&title=) if you got any ideas for more examples! + +## Browser support + +This library will always produce an ESNext bundle. +If you want to support legacy browsers, please use the feature of your bundler to transpile dependencies. +In case of webpack and babel (give [vite](https://vitejs.dev/) a try, it's awesome) you'll have to install corresponding plugins such as [babel-plugin-proposal-optional-chaining](https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining) and include the dependency from `node_modules` which is normally entirely excluded from being processed. + +I do this to provide maximum flexibility and give those who target ESNext a chance to make full use of how this library is bundled. +Everything else is just a matter of configuration :) + +## Preventing text selection To not interfere with text-selection, selection-js won't prevent any default events anymore (as of `v2.0.3`). This, however, can cause problems with the actual selection ("introduced" by [#99](https://github.com/simonwep/viselect/pull/99), reported in [#103](https://github.com/simonwep/viselect/issues/103)). @@ -13,3 +32,91 @@ If you don't care about text-selection, add the following to the container where user-select: none; } ``` + +Another solution is to make the document during a selection non-selectable: + +```ts +selection + .on('beforestart', () => document.body.style.userSelect = 'none') + .on('stop', () => document.body.style.userSelect = 'unset'); +``` + +Issues: [#103](https://github.com/simonwep/viselect/issues/103) + +## Changing selectables during a selection + +In some cases you may add / remove selectables during a selection, especially when it comes to scrolling. +In this case make sure to call `selection.resolveSelectables()` every time you add / remove a selectable so that viselect is aware of the change. +Consult the [api reference](./api-reference.md) for more information. + + +## Allowing the user to scroll with two fingers + +```js +selection.on('beforestart', (() => { + let timeout = null; + + return ({event}) => { + + // Check if user already tapped inside of a selection-area. + if (timeout !== null) { + + // A second pointer-event occured, ignore that one. + clearTimeout(timeout); + timeout = null; + } else { + + // Wait 50ms in case the user uses two fingers to scroll. + timeout = setTimeout(() => { + + // OK User used only one finger, we can safely initiate a selection and reset the timer. + selection.trigger(event); + timeout = null; + }, 50); + } + + // Never start automatically. + return false; + }; +})()); +``` + +Issues: [#70](https://github.com/simonwep/viselect/issues/70) + +## Preventing the start of a selection based on certain conditions + +```js +selection.on('beforestart', ({event}) => { + return !event.path.some(item => { + + // item is in this case an element affected by the event-bubbeling. + // To exclude elements with class "blocked" you could do the following (#73): + return item.classList.contains('blocked'); + + // If the areas you're using contains input elements you might want to prevent + // any out-going selections from these elements (#72): + return event.target.tagName !== 'INPUT'; + }); +}); +``` + +Issues: [#73](https://github.com/simonwep/viselect/issues/73) + +## Preventing select from right click or more + +This is now a default feature with the `triggers` option, see [API reference](./api-reference.md#selectionoptions)! + +```js +selection.on('beforestart', (event) => { + const allowedButtons = [ + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + 1, // left click + 2, // right click + 4, // mouse wheel / middle button + ]; + + return allowedButtons.includes(event.event.buttons); +}); +``` + +Issues: [#101](https://github.com/simonwep/viselect/issues/101) diff --git a/docs/pages/frameworks/preact.md b/docs/pages/frameworks/preact.md index 2311773..9b27104 100644 --- a/docs/pages/frameworks/preact.md +++ b/docs/pages/frameworks/preact.md @@ -1,14 +1,15 @@ # Using Viselect with Preact -::: tip -This is merely a convenience wrapper around the [core library](./vanilla.md). -The core API is fairly simple, if you want to have full control over it, you should [roll out your own wrapper](../custom-integration.md) in your app. -Don't worry, it's not that hard! -::: + + +## Source Code + +You can find the source code for this component [here](https://github.com/simonwep/viselect/blob/master/packages/preact/src/SelectionArea.tsx). +You can use it as template for your own implementation. ## Installation -To use Viselect with Preact, install its Preact package with: +To use Viselect with Preact, install its preact package with: ::: code-group @@ -30,10 +31,14 @@ $ yarn add @viselect/preact You can use Viselect in your Preact project by importing the `SelectionArea` component from the `@viselect/preact` package. -::: tip -All options are exposed as props -They're a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! -::: +> [!TIP] +> All options are exposed as props. +> They're a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! + +> [!NOTE] +> Changing the props won't do anything as React doesn't deep-compare props. +> Trigger a re-render by adding and changing the key of the `SelectionArea` component. +> See [#226](https://github.com/simonwep/viselect/issues/226). ::: code-group @@ -44,75 +49,75 @@ import {useState} from 'preact/hooks'; import './styles.css'; const App: FunctionComponent = () => { - const [selected, setSelected] = useState>(() => new Set()); - - const extractIds = (els: Element[]): number[] => - els.map(v => v.getAttribute('data-key')) - .filter(Boolean) - .map(Number); - - const onStart = ({event, selection}: SelectionEvent) => { - if (!event?.ctrlKey && !event?.metaKey) { - selection.clearSelection(); - setSelected(() => new Set()); - } - }; - - const onMove = ({store: {changed: {added, removed}}}: SelectionEvent) => { - setSelected(prev => { - const next = new Set(prev); - extractIds(added).forEach(id => next.add(id)); - extractIds(removed).forEach(id => next.delete(id)); - return next; - }); - }; - - return ( - <> - - {new Array(42).fill(0).map((_, index) => ( -
- ))} - - - ); + const [selected, setSelected] = useState>(() => new Set()); + + const extractIds = (els: Element[]): number[] => + els.map(v => v.getAttribute('data-key')) + .filter(Boolean) + .map(Number); + + const onStart = ({event, selection}: SelectionEvent) => { + if (!event?.ctrlKey && !event?.metaKey) { + selection.clearSelection(); + setSelected(() => new Set()); + } + }; + + const onMove = ({store: {changed: {added, removed}}}: SelectionEvent) => { + setSelected(prev => { + const next = new Set(prev); + extractIds(added).forEach(id => next.add(id)); + extractIds(removed).forEach(id => next.delete(id)); + return next; + }); + }; + + return ( + <> + + {new Array(42).fill(0).map((_, index) => ( +
+ ))} + + + ); } ``` ```css [styles.css] .container { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - border: 1px dashed #4f5276; - border-radius: 15px; - padding: 15px; - margin: 15px 0; - user-select: none; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + border: 1px dashed #4f5276; + border-radius: 15px; + padding: 15px; + margin: 15px 0; + user-select: none; } .container div { - height: 50px; - width: 50px; - margin: 3px; - background: rgba(66, 68, 90, 0.075); - border-radius: 10px; - cursor: pointer; + height: 50px; + width: 50px; + margin: 3px; + background: rgba(66, 68, 90, 0.075); + border-radius: 10px; + cursor: pointer; } div.selected { - background: linear-gradient(45deg, #78b2ff, #218ad9); + background: linear-gradient(45deg, #78b2ff, #218ad9); } .selection-area { - background: rgba(46, 115, 252, 0.11); - border: 1px solid rgba(98, 155, 255, 0.85); - border-radius: 0.15em; + background: rgba(46, 115, 252, 0.11); + border: 1px solid rgba(98, 155, 255, 0.85); + border-radius: 0.15em; } ``` diff --git a/docs/pages/frameworks/react.md b/docs/pages/frameworks/react.md index ec9a6cb..8a97463 100644 --- a/docs/pages/frameworks/react.md +++ b/docs/pages/frameworks/react.md @@ -1,14 +1,10 @@ # Using Viselect with React -::: tip -This is merely a convenience wrapper around the [core library](./vanilla.md). -The core API is fairly simple, if you want to have full control over it, you should [roll out your own wrapper](../custom-integration.md) in your app. -Don't worry, it's not that hard! -::: + ## Installation -To use Viselect with React, install its React package with: +To use Viselect with React, install its react package with: ::: code-group @@ -30,10 +26,14 @@ $ yarn add @viselect/react You can use Viselect in your React project by importing the `SelectionArea` component from the `@viselect/react` package. -::: tip -All options are exposed as props -They're a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! -::: +> [!TIP] +> All options are exposed as direct props. +> They're a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! + +> [!NOTE] +> Changing the props won't do anything as React doesn't deep-compare props. +> Trigger a re-render by adding and changing the key of the `SelectionArea` component. +> See [#226](https://github.com/simonwep/viselect/issues/226). ::: code-group @@ -43,75 +43,75 @@ import React, {FunctionComponent, useState} from 'react'; import './styles.css'; const App: FunctionComponent = () => { - const [selected, setSelected] = useState>(() => new Set()); - - const extractIds = (els: Element[]): number[] => - els.map(v => v.getAttribute('data-key')) - .filter(Boolean) - .map(Number); - - const onStart = ({ event, selection }: SelectionEvent) => { - if (!event?.ctrlKey && !event?.metaKey) { - selection.clearSelection(); - setSelected(() => new Set()); - } - }; - - const onMove = ({ store: { changed: { added, removed } } }: SelectionEvent) => { - setSelected(prev => { - const next = new Set(prev); - extractIds(added).forEach(id => next.add(id)); - extractIds(removed).forEach(id => next.delete(id)); - return next; - }); - }; - - return ( - <> - - {new Array(42).fill(0).map((_, index) => ( -
- ))} - - - ); + const [selected, setSelected] = useState>(() => new Set()); + + const extractIds = (els: Element[]): number[] => + els.map(v => v.getAttribute('data-key')) + .filter(Boolean) + .map(Number); + + const onStart = ({ event, selection }: SelectionEvent) => { + if (!event?.ctrlKey && !event?.metaKey) { + selection.clearSelection(); + setSelected(() => new Set()); + } + }; + + const onMove = ({ store: { changed: { added, removed } } }: SelectionEvent) => { + setSelected(prev => { + const next = new Set(prev); + extractIds(added).forEach(id => next.add(id)); + extractIds(removed).forEach(id => next.delete(id)); + return next; + }); + }; + + return ( + <> + + {new Array(42).fill(0).map((_, index) => ( +
+ ))} + + + ); } ``` ```css [styles.css] .container { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - border: 1px dashed #4f5276; - border-radius: 15px; - padding: 15px; - margin: 15px 0; - user-select: none; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + border: 1px dashed #4f5276; + border-radius: 15px; + padding: 15px; + margin: 15px 0; + user-select: none; } .container div { - height: 50px; - width: 50px; - margin: 3px; - background: rgba(66, 68, 90, 0.075); - border-radius: 10px; - cursor: pointer; + height: 50px; + width: 50px; + margin: 3px; + background: rgba(66, 68, 90, 0.075); + border-radius: 10px; + cursor: pointer; } div.selected { - background: linear-gradient(45deg, #78b2ff, #218ad9); + background: linear-gradient(45deg, #78b2ff, #218ad9); } .selection-area { - background: rgba(46, 115, 252, 0.11); - border: 1px solid rgba(98, 155, 255, 0.85); - border-radius: 0.15em; + background: rgba(46, 115, 252, 0.11); + border: 1px solid rgba(98, 155, 255, 0.85); + border-radius: 0.15em; } ``` diff --git a/docs/pages/frameworks/vanilla.md b/docs/pages/frameworks/vanilla.md index 99e8a25..0913c04 100644 --- a/docs/pages/frameworks/vanilla.md +++ b/docs/pages/frameworks/vanilla.md @@ -18,17 +18,21 @@ $ pnpm add -D @viselect/vanilla $ yarn add -D @viselect/vanilla ``` +```html [script] + +``` + +```js [module] +import SelectionArea from "https://cdn.jsdelivr.net/npm/@viselect/vanilla/dist/viselect.mjs" +``` + ::: ## Usage -As per our [quickstart](/pages/quickstart.md), you can use Viselect in your project by importing the `SelectionArea` class from the `@viselect/vanilla` package. +As per our [quickstart](../quickstart.md), you can use Viselect in your project by importing the `SelectionArea` class from the `@viselect/vanilla` package. For all the options available, check the [API reference](../api-reference.md#selectionoptions). -::: tip -As already mentioned, it's recommended to start from here _even_ if you're using vue, react, preact or any other framework as the only difference is to take care of the instance creation and destruction. -::: - ::: code-group ```ts [main.ts] @@ -52,7 +56,7 @@ const selection = new SelectionArea({ selectables: ['.container > div'], // Specifies the elements that can be selected boundaries: ['.container'], // Specifies the boundaries of each selection }).on('start', ({ store, event }) => { - if (!event.ctrlKey && !event.metaKey) { + if (!event.ctrlKey && !event.metaKey) { // Clear selection if no modifier key is pressed store.stored.forEach(el => el.classList.remove('selected')); selection.clearSelection(); } @@ -64,37 +68,37 @@ const selection = new SelectionArea({ ```css [styles.css] .container { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - border: 1px dashed #4f5276; - border-radius: 15px; - padding: 15px; - margin: 15px 0; - user-select: none; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + border: 1px dashed #4f5276; + border-radius: 15px; + padding: 15px; + margin: 15px 0; + user-select: none; } .container div { - height: 50px; - width: 50px; - margin: 3px; - background: rgba(66, 68, 90, 0.075); - border-radius: 10px; - cursor: pointer; + height: 50px; + width: 50px; + margin: 3px; + background: rgba(66, 68, 90, 0.075); + border-radius: 10px; + cursor: pointer; } .container.green div.selected { - background: linear-gradient(45deg, #78b2ff, #218ad9); + background: linear-gradient(45deg, #78b2ff, #218ad9); } .container.blue div.selected { - background: linear-gradient(45deg, #9e91ef, #5c51b4); + background: linear-gradient(45deg, #9e91ef, #5c51b4); } .selection-area { - background: rgba(46, 115, 252, 0.11); - border: 1px solid rgba(98, 155, 255, 0.85); - border-radius: 0.15em; + background: rgba(46, 115, 252, 0.11); + border: 1px solid rgba(98, 155, 255, 0.85); + border-radius: 0.15em; } ``` diff --git a/docs/pages/frameworks/vue.md b/docs/pages/frameworks/vue.md index 5b9c303..c1ceb8b 100644 --- a/docs/pages/frameworks/vue.md +++ b/docs/pages/frameworks/vue.md @@ -1,14 +1,15 @@ # Using Viselect with Vue -::: tip -This is merely a convenience wrapper around the [core library](./vanilla.md). -The core API is fairly simple, if you want to have full control over it, you should [roll out your own wrapper](../custom-integration.md) in your app. -Don't worry, it's not that hard! -::: + + +## Source Code + +You can find the source code for this component [here](https://github.com/simonwep/viselect/blob/master/packages/vue/src/SelectionArea.vue). +You can use it as template for your own implementation. ## Installation -To use Viselect with Vue, install its Vue package with: +To use Viselect with Vue, install its vue package with: ::: code-group @@ -30,14 +31,12 @@ $ yarn add @viselect/vue You can use Viselect in your Vue project by importing the `SelectionArea` component from the `@viselect/vue` package. -::: tip -All options are exposed as `options` prop. -They're a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! -::: +> [!TIP] +> All options are exposed as `options` prop, events can be passed as props suffixed with `on`. +> The options are a one-to-one mapping of the original options describe [here](../api-reference.md#selectionoptions)! -::: info -Events are handled using props because you can’t return a value in events synchronously! -::: +> [!NOTE] +> Events are handled using props because you can’t return a value in events synchronously. ```vue [App.vue]