Skip to content
Open
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
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"eslint": "^8.57.1",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-vue": "^9.28.0",
"typescript": "^5.6.3",
"vite": "^5.4.7",
"vite-plugin-dts": "^4.3.0",
"vue": "^3.5.7"
},
"peerDependencies": {
Expand All @@ -27,10 +29,12 @@
],
"main": "./dist/dnd-grid.umd.cjs",
"module": "./dist/dnd-grid.js",
"types": "./dist/dnd-grid.d.ts",
"exports": {
".": {
"import": "./dist/dnd-grid.js",
"require": "./dist/dnd-grid.umd.cjs"
"require": "./dist/dnd-grid.umd.cjs",
"types": "./dist/dnd-grid.d.ts"
},
"./style.css": "./dist/style.css",
"./*": "./src/*/lib.js"
Expand Down
499 changes: 491 additions & 8 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { shallowRef, computed } from 'vue'
import GridContainer from './components/Container.vue'
import GridBox from './components/Box.vue'
import initialLayout from './layout.json'
import * as l from './tools/layout.js'
import * as l from './tools/layout'

const cellWidthRef = shallowRef()
const cellMaxWidthRef = shallowRef()
Expand Down
48 changes: 25 additions & 23 deletions src/components/Box.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<script>
<script lang="ts">
export default {
inheritAttrs: false
}
</script>

<script setup>
import { ContainerSymbol } from '../symbols.js'
<script setup lang="ts">
import { ContainerSymbol } from '../symbols'
import { inject, useCssModule, shallowRef, computed, onScopeDispose } from 'vue'
import { toPixels, fromPixels } from '../tools/layout.js'
import useDndHandler from '../composables/useDndHandler.js'
import { toPixels, fromPixels, GridPosition, PixelPosition } from '../tools/layout'
import useDndHandler from '../composables/useDndHandler'

const props = defineProps({
boxId: {
required: true,
type: null
type: null as any,
},

overflow: {
Expand All @@ -36,15 +36,15 @@ const {
updateBox,
startLayout,
stopLayout,
} = inject(ContainerSymbol)
} = inject(ContainerSymbol)!

const overlayEl = document.createElement('div')
overlayEl.classList.add($style.overlay)

const slotContainerElRef = shallowRef()
const boxElRef = shallowRef()

const boxRef = computed(() => getBox(props.boxId, true))
const boxRef = computed(() => getBox(props.boxId)!)
const visibleRef = computed(() => boxRef.value && !(boxRef.value.hidden ?? false))

// grid mode
Expand Down Expand Up @@ -81,21 +81,23 @@ const cssPixelsRef = computed(() => {
})

const isBoxResizableRef = computed(() => {
return !disabledRef.value // dnd is enabled
return (!disabledRef.value // dnd is enabled
&& isResizableRef.value // resizing is enabled
&& (boxRef.value?.isResizable ?? true) // box resizing is enabled (defaults to enabled)
&& (!boxRef.value?.pinned || boxRef.value?.isResizable) // pinned boxes can only be dragged when resizing is explicitly enabled
) ?? false
})

const isBoxDraggableRef = computed(() => {
return !disabledRef.value // dnd is enabled
return (!disabledRef.value // dnd is enabled
&& isDraggableRef.value // dragging is enabled
&& (boxRef.value?.isDraggable ?? true) // box dragging is enabled (defaults to enabled)
&& (!boxRef.value?.pinned || boxRef.value?.isDraggable) // pinned boxes can only be dragged when dragging is explicitly enabled
) ?? false
})

const baseCssPixelsRef = shallowRef({})
let basePosition
const baseCssPixelsRef = shallowRef({} as { x: string, y: string, w: string, h: string })
let basePosition: GridPosition | undefined

const isDraggingRef = shallowRef(false)
const dragEvents = useDndHandler({
Expand All @@ -122,19 +124,19 @@ const dragEvents = useDndHandler({
},
update: function onDragUpdate ({ offsetX, offsetY }) {
let offsetPixels = { x: offsetX, y: offsetY, w: 0, h: 0 }
applyOffsetPixels(basePosition, offsetPixels)
applyOffsetPixels(basePosition!, offsetPixels)
}
})

const isResizingRef = shallowRef(false)
let resizeMode
let resizeMode: undefined | "t-" | "-r" | "b-" | "-l" | "tl" | "tr" | "br" | "bl"
const resizeEvents = useDndHandler({
allow: function allowResize (evt) {
return isBoxResizableRef.value && canStartResize(evt)
},
start: function onResizeStart (_, evt) {
startLayout()
resizeMode = evt?.target?.getAttribute?.('dnd-grid-resize') || 'br'
resizeMode = (evt?.target as Element | undefined)?.getAttribute?.('dnd-grid-resize') as typeof resizeMode || 'br'
baseCssPixelsRef.value = cssPixelsRef.value
basePosition = positionRef.value
isResizingRef.value = true
Expand Down Expand Up @@ -176,15 +178,15 @@ const resizeEvents = useDndHandler({
break
}

applyOffsetPixels(basePosition, offsetPixels)
applyOffsetPixels(basePosition!, offsetPixels)
}
})

const boxEventsRef = computed(() => {
return mergeEvents(dragEvents, resizeEvents)
})

function applyOffsetPixels (basePosition, offsetPixels) {
function applyOffsetPixels (basePosition: GridPosition, offsetPixels: PixelPosition) {
const slotContainerEl = slotContainerElRef.value
slotContainerEl?.style?.setProperty('--dnd-grid-box-offset-left', `${offsetPixels.x}px`)
slotContainerEl?.style?.setProperty('--dnd-grid-box-offset-top', `${offsetPixels.y}px`)
Expand Down Expand Up @@ -215,7 +217,7 @@ function applyOffsetPixels (basePosition, offsetPixels) {
updatePosition(targetPosition)
}

function updatePosition (targetPosition) {
function updatePosition (targetPosition: GridPosition) {
const position = positionRef.value
if (
position.x !== targetPosition.x ||
Expand All @@ -227,17 +229,17 @@ function updatePosition (targetPosition) {
}
}

function mergeEvents (...eventObjects) {
const eventMap = new Map()
function mergeEvents (...eventObjects: { [key: string]: (event: any) => void }[]) {
const eventMap = new Map<string, ((event: any) => void)[]>()
eventObjects.forEach(eventObject => {
for (const key in eventObject) {
const callbackList = eventMap.get(key) || eventMap.set(key, []).get(key)
const callbackList = eventMap.get(key) || eventMap.set(key, []).get(key)!
callbackList.push(eventObject[key])
}
})
const mergedEvents = {}
const mergedEvents: { [key: string]: any } = {}
eventMap.forEach((callbacks, key) => {
mergedEvents[key] = evt => callbacks.forEach(callback => callback(evt))
mergedEvents[key] = (evt: any) => callbacks.forEach(callback => callback(evt))
})
return mergedEvents
}
Expand Down
71 changes: 38 additions & 33 deletions src/components/Container.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
<script>
<script lang="ts">
export default {
inheritAttrs: false
}

let NEXT_DND_GRID_ID = 1
</script>

<script setup>
import { provide, readonly, useCssModule, watch, onMounted, onBeforeUnmount, toRef, shallowRef, computed } from 'vue'
import { ContainerSymbol } from '../symbols.js'
import { getBox as _getBox, updateBox as _updateBox } from '../tools/layout.js'
<script setup lang="ts">
import { provide, readonly, useCssModule, watch, onMounted, onBeforeUnmount, toRef, shallowRef, computed, Prop, Ref } from 'vue'
import { ContainerSymbol } from '../symbols'
import { GridPosition, Layout, LayoutElement, PartialLayoutElement, getBox as _getBox, updateBox as _updateBox } from '../tools/layout'

type SelectorProp = {
include: string,
exclude: string,
}

const props = defineProps({
layout: {
type: Array,
default: () => []
},
} as Prop<Layout>,

bubbleUp: {
type: [Boolean, String],
default: false
},
} as Prop<boolean | "jump-over">,

disabled: {
type: Boolean,
Expand All @@ -43,15 +48,15 @@ const props = defineProps({
include: '[dnd-grid-drag]',
exclude: ':is(input, button, select, a[href])'
})
},
} as Prop<SelectorProp>,

resizeSelector: {
type: Object,
default: () => ({
include: '[dnd-grid-resize]',
exclude: ':is(input, button, select, a[href])'
})
},
} as Prop<SelectorProp>,

addResizeHandles: {
type: Boolean,
Expand Down Expand Up @@ -123,49 +128,49 @@ const $style = useCssModule()

const containerElRef = shallowRef()
const computedCellSizeRef = shallowRef()
const modeRef = shallowRef('grid')
const layoutRef = shallowRef(props.layout)
const modeRef = shallowRef<'grid' | 'layout'>('grid')
const layoutRef = shallowRef(props.layout!)

provide(ContainerSymbol, {
layout: readonly(layoutRef),
mode: readonly(modeRef),
disabled: toRef(() => props.disabled),
isResizable: toRef(() => props.isResizable),
isDraggable: toRef(() => props.isDraggable),
disabled: toRef(() => props.disabled!),
isResizable: toRef(() => props.isResizable!),
isDraggable: toRef(() => props.isDraggable!),
computedCellSize: readonly(computedCellSizeRef),
startLayout,
stopLayout,
getBox,
updateBox,
canStartDrag,
canStartResize,
addResizeHandles: toRef(() => props.addResizeHandles)
addResizeHandles: toRef(() => props.addResizeHandles!)
})

watch(() => props.layout, newLayout => {
watch(() => props.layout!, newLayout => {
layoutRef.value = newLayout
})

const layoutOptionsRef = computed(() => {
return {
bubbleUp: props.bubbleUp
bubbleUp: props.bubbleUp!
}
})

const dragSelectorsRef = computed(() => {
return getSelectorsFromProp(props.dragSelector)
return getSelectorsFromProp(props.dragSelector!)
})

const resizeSelectorsRef = computed(() => {
return getSelectorsFromProp(props.resizeSelector)
return getSelectorsFromProp(props.resizeSelector!)
})

const cursorStyleContentRef = computed(() => {
if (props.disabled) {
return ''
}

const styleContent = []
const styleContent: string[] = []

styleContent.push(
...[
Expand All @@ -175,7 +180,7 @@ const cursorStyleContentRef = computed(() => {
[':where([dnd-grid-resize=tl], [dnd-grid-resize=br])', 'cursor: var(--dnd-resize-cursor-nwse, nwse-resize);'],
[':where([dnd-grid-resize=tr], [dnd-grid-resize=bl])', 'cursor: var(--dnd-resize-cursor-nesw, nesw-resize);']
].map(([selector, rules]) => {
const selectors = getSelectorsFromProp(props.resizeSelector, selector)
const selectors = getSelectorsFromProp(props.resizeSelector!, selector)
return `
.${$style.container}[dnd-grid-id="${DND_GRID_ID}"] :not(.${$style.container}) ${selectors.join(', ')} {
${rules}
Expand All @@ -185,7 +190,7 @@ const cursorStyleContentRef = computed(() => {
...[
['', 'cursor: var(--dnd-drag-cursor, move);']
].map(([selector, rules]) => {
const selectors = getSelectorsFromProp(props.dragSelector, selector)
const selectors = getSelectorsFromProp(props.dragSelector!, selector)
return `
.${$style.container}[dnd-grid-id="${DND_GRID_ID}"] :not(.${$style.container}) ${selectors.join(', ')} {
${rules}
Expand Down Expand Up @@ -218,17 +223,17 @@ onBeforeUnmount(() => {
}
})

function getBox (id) {
return _getBox(layoutRef.value, id, layoutOptionsRef.value)
function getBox (id: any) {
return _getBox(layoutRef.value, id)
}

function updateBox (id, data) {
return layoutRef.value = _updateBox(props.layout, id, data, layoutOptionsRef.value)
function updateBox (id: any, data: PartialLayoutElement) {
return layoutRef.value = _updateBox(props.layout!, id, data, layoutOptionsRef.value)
}

function toCssSize (value) {
function toCssSize (value: string | number | null | undefined) {
if (value == undefined) return
return isNaN(value) ? value : `${value}px`
return isNaN(value as number) ? value : `${value}px`
}

function updateComputedCellSize () {
Expand All @@ -253,15 +258,15 @@ function stopLayout () {
modeRef.value = 'grid'
}

function canStartDrag (evt) {
return evt.target && dragSelectorsRef.value.find(selector => evt.target.matches(selector))
function canStartDrag (evt: MouseEvent | TouchEvent) {
return Boolean(evt.target && dragSelectorsRef.value.find(selector => (evt.target as Element).matches(selector)))
}

function canStartResize (evt) {
return evt.target && resizeSelectorsRef.value.find(selector => evt.target.matches(selector))
function canStartResize (evt: MouseEvent | TouchEvent) {
return Boolean(evt.target && resizeSelectorsRef.value.find(selector => (evt.target as Element).matches(selector)))
}

function getSelectorsFromProp (prop, additionalSelector) {
function getSelectorsFromProp (prop: SelectorProp, additionalSelector?: string) {
let selectors = [
(prop.include || '*') + (additionalSelector || ''),
(prop.include || '*') + (additionalSelector || '') + ' *'
Expand Down
File renamed without changes.
1 change: 0 additions & 1 deletion src/composables/lib.js

This file was deleted.

1 change: 1 addition & 0 deletions src/composables/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useDndHandler } from './useDndHandler'
Loading