Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ Here is the full list:
|dropInsideThreshold |number |How much % of the item has to be inside the dropzone to be considered inside (0 = barely touching, 1 = completely inside) |`1`
|dropTargetThreshold |number |How much % of the zone does the pointer has to be in to be considered a target (0 = anywhere in the zone, max: 0.5 = has to point at the center of the zone) |`0`
|usePointerEvents |boolean |Whether to use Pointer Events to replace traditional Mouse or Touch Events. Useful for tools like Google Blockly. |`false`
|multiSelectIgnoreParents |boolean |Whether or not to ignore parent elements and only select children when they intersect while multi-selecting. Useful for tools like Google Blockly. |`false`
|selectedClass |string |The class name assigned to the selected items. |[see classes](#classes)
|hoverClass |string |The class name assigned to the mouse hovered items. |[see classes](#classes)
|selectorClass |string |The class name assigned to the square selector helper. |[see classes](#classes)
Expand Down
17 changes: 17 additions & 0 deletions __tests__/functional/multiselection.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
<button type="button" id="five3" class="item c five">5</button>
</div>

Multi-Select-Ignore-Parents: ON
<div>
<button type="button" id="one4" class="item d one">1</button>
<div style="display: inline;">
<button type="button" id="two4" class="item d two" style="left: -20px;">2</button>
</div>
</div>

<script src="../../dist/DragSelect.js"></script>
<script>
window.selected = [];
Expand All @@ -52,3 +60,12 @@
});
ds3.subscribe('callback', ({items}) => window.multiSelectTogglingOff = items.map(item => item.id))
</script>
<script>
window.multiSelectIgnoreParents = [];

const ds4 = new DragSelect({
selectables: document.querySelectorAll('.item.d'),
multiSelectIgnoreParents: true,
});
ds4.subscribe('callback', ({items}) => window.multiSelectIgnoreParents = items.map(item => item.id))
</script>
40 changes: 40 additions & 0 deletions __tests__/functional/multiselection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,44 @@ describe('Multiselection', () => {
const expected = ['one3', 'two3', 'four3', 'three3']
expect(multiSelectTogglingOff.sort()).toEqual(expected.sort())
})

it('multiSelect-ingore-parents should only select the child when intersecting with parent and the selection area is within the child', async () => {
await page.goto(`${baseUrl}/multiselection.html`)

const mouse = page.mouse
const keyboard = page.keyboard
await keyboard.down('Shift')
await mouse.move(50, 240, { steps: 10 })
await mouse.down()
await mouse.move(40, 240, { steps: 10 })
await mouse.up()
await wait(100)

const { multiSelectIgnoreParents } = await page.evaluate(() => ({
multiSelectIgnoreParents,
}))

const expected = ['two4']
expect(multiSelectIgnoreParents).toEqual(expected)
})

it('multiSelect-ingore-parents should select both when the selection area is not completely inside the child', async () => {
await page.goto(`${baseUrl}/multiselection.html`)

const mouse = page.mouse
const keyboard = page.keyboard
await keyboard.down('Shift')
await mouse.move(50, 240, { steps: 10 })
await mouse.down()
await mouse.move(20, 240, { steps: 10 })
await mouse.up()
await wait(100)

const { multiSelectIgnoreParents } = await page.evaluate(() => ({
multiSelectIgnoreParents,
}))

const expected = ['one4', 'two4']
expect(multiSelectIgnoreParents.sort()).toEqual(expected.sort())
})
})
35 changes: 35 additions & 0 deletions src/methods/filterParent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @ts-check
import '../types'
import { isCollision } from '.'

/**
* Logic for checking and filtering out elements that are
* parents of other selected elements as well as intersecting
* @param {Array} list
* @param {Array} listRects
* @param {DSBoundingRect | DOMRect} rect
* @returns {Array}
*/
export default (list, listRects, rect) => {
const toRemove = []
for (let i = 0; i < list.length; i++)
for (let j = 0; j < list.length; j++) {
const parent = list[i]
const parentRect = listRects[i]
const child = list[j]
const childRect = listRects[j]
if (parent === child || !isCollision(childRect, parentRect, 0) ||
parent.parentNode === null || child.parentNode === null ||
parent.parentNode === child.parentNode)
continue
else if (parent.parentNode.contains(child.parentNode) &&
// Continue to select if user draws a rectangle that
// covers more than then child ifself
isCollision(rect, childRect, 1)) {
toRemove.push(parent)
break
}
}

return toRemove
}
6 changes: 6 additions & 0 deletions src/methods/hydrateSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ export default (settings, withFallback) => ({
withFallback,
false
),
...hydrateHelper(
'multiSelectIgnoreParents',
settings.multiSelectIgnoreParents,
withFallback,
false
),
...hydrateHelper('hoverClass', settings.hoverClass, withFallback, 'ds-hover'),
...hydrateHelper(
'selectableClass',
Expand Down
1 change: 1 addition & 0 deletions src/methods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as createSelectorAreaElement } from './createSelectorAreaElemen
export { default as createSelectorElement } from './createSelectorElement'
export { default as debounce } from './debounce'
export { default as documentScroll } from './documentScroll'
export { default as filterParent } from './filterParent'
export { default as getAllParentNodes } from './getAllParentNodes'
export { default as getAreaRect } from './getAreaRect'
export { default as getCurrentScroll } from './getCurrentScroll'
Expand Down
17 changes: 14 additions & 3 deletions src/modules/Selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import '../types'
import DragSelect from '../DragSelect'

import { isCollision, handleSelection, handleUnSelection } from '../methods'
import { isCollision, handleSelection, filterParent, handleUnSelection } from '../methods'

export default class Selection {
/**
Expand Down Expand Up @@ -63,13 +63,16 @@ export default class Selection {
/** @type {any} */
const elRects = SelectableSet.rects

const select = []
let select = []
const selectRec = []
const unselect = []

for (const [element, rect] of elRects) {
if (!SelectorArea.isInside(element, rect)) continue
if (isCollision(rect, Selector.rect, this.Settings.selectionThreshold))
if (isCollision(rect, Selector.rect, this.Settings.selectionThreshold)) {
select.push(element)
selectRec.push(rect)
}
else unselect.push(element)
}

Expand All @@ -78,6 +81,14 @@ export default class Selection {
this.Settings.multiSelectToggling

if (this.DS.continue) return

// Filter out elements that are parents of other selected elements when they intersect
if (this.Settings.multiSelectIgnoreParents) {
const toRemove = filterParent(select, selectRec, Selector.rect)
select = select.filter(el => !toRemove.includes(el))
unselect.push(...toRemove)
}

select.forEach((element) =>
handleSelection({
element,
Expand Down
1 change: 1 addition & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* @property {number} [dropInsideThreshold=1] how much % of the item has to be inside the dropzone to be considered inside (0 = barely touching, 1 = completely inside)
* @property {number} [dropTargetThreshold=0] how much % of the zone does the pointer has to be in to be considered a target (0 = anywhere in the zone, max: 0.5 = has to point at the center of the zone)
* @property {boolean} [usePointerEvents=false] Whether to use Pointer Events to replace traditional Mouse or Touch Events. Useful for tools like Google Blockly.
* @property {boolean} [multiSelectIgnoreParents=false] Whether or not to ignore parent elements and only select children when they intersect while multi-selecting. Useful for tools like Google Blockly.
* @property {string} [hoverClass=ds-hover] the class assigned to the mouse hovered items
* @property {string} [selectableClass=ds-selectable] the class assigned to the elements that can be selected
* @property {string} [selectedClass=ds-selected] the class assigned to the selected items
Expand Down
8 changes: 5 additions & 3 deletions www/docs/API/Settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ const ds = new DragSelect({
## Miscellaneous
| property | type | default | description
|--- |--- |--- |---
| `refreshMemoryRate` | number (milliseconds) | 80 | Refresh rate on memoization, higher numbers mean better performance but more lag if elements are moving while interacting/selecting, lower numbers mean less lag but worse performance. If none of your DOMNodes are moving, you can set it to a very high number to increase performance
| `usePointerEvents` | boolean | false | Whether to use Pointer Events to replace traditional Mouse or Touch Events. Useful for tools like Google Blockly.
| `zoom` | number | 1 | Zoom scale factor (in case of using CSS style transform: scale() which messes with real positions). Unit scale zoom. (deprecated)
| `refreshMemoryRate` | number (milliseconds) | 80 | Refresh rate on memoization, higher numbers mean better performance but more lag if elements are moving while interacting/selecting, lower numbers mean less lag but worse performance. If none of your DOMNodes are moving, you can set it to a very high number to increase performance
| `usePointerEvents` | boolean | false | Whether to use Pointer Events to replace traditional Mouse or Touch Events. Useful for tools like Google Blockly.
| `multiSelectIgnoreParents`| boolean | false | Whether or not to ignore parent elements and only select children when they intersect while multi-selecting. Useful for tools like Google Blockly.
| `zoom` | number | 1 | Zoom scale factor (in case of using CSS style transform: scale() which messes with real positions). Unit scale zoom. (deprecated)

## DragSelect Example with all Props

Expand Down Expand Up @@ -112,6 +113,7 @@ new DragSelect({
dropZoneInsideClass: 'ds-dropzone-inside',
refreshMemoryRate: 80,
usePointerEvents: false,
multiSelectIgnoreParents: false,
zoom: 1,
});
```