Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Deploy Docs to GitHub Pages

on:
release:
types: [published]
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
Expand Down
40 changes: 37 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
name: Publish to npm

on:
release:
types: [published]
push:
tags:
- 'v*'

jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Check formatting
run: npm run format:check

- name: Lint
run: npm run lint

- name: Build library
run: npm run build

- name: Build docs
run: npm run build:docs

publish:
needs: ci
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
id-token: write
steps:
- name: Checkout
Expand All @@ -31,3 +60,8 @@ jobs:
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release

# Runs when a release/* branch is merged into main.
# Reads the version from package.json, updates index.html,
# commits the change, tags main, and pushes — triggering
# the publish and deploy-docs workflows.

on:
pull_request:
types: [closed]
branches: [main]

jobs:
tag:
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Read version from package.json
id: version
run: echo "version=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"

- name: Update version in index.html
run: |
VERSION="${{ steps.version.outputs.version }}"
sed -i "s/\"softwareVersion\": \"[^\"]*\"/\"softwareVersion\": \"$VERSION\"/" index.html

- name: Check for changes
id: diff
run: |
if git diff --quiet; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Commit version update
if: steps.diff.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add index.html
git commit -m "chore: update version to ${{ steps.version.outputs.version }}"
git push

- name: Create and push tag
run: |
git tag "v${{ steps.version.outputs.version }}"
git push origin "v${{ steps.version.outputs.version }}"
7 changes: 1 addition & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"description": "React component library for drag-and-drop row and column reordering in tables. Features 60fps animations, auto-scroll, virtual scrolling for 100k+ rows, mobile long-press support, drag handles, and full style customization.",
"url": "https://samiodeh1337.github.io/react-table-dnd/",
"downloadUrl": "https://www.npmjs.com/package/react-table-dnd",
"softwareVersion": "1.0.0",
"softwareVersion": "2.0.8",
"programmingLanguage": ["TypeScript", "React"],
"license": "https://opensource.org/licenses/MIT",
"author": {
Expand All @@ -83,11 +83,6 @@
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5",
"ratingCount": "1"
}
}
</script>
Expand Down
2 changes: 1 addition & 1 deletion public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://samiodeh1337.github.io/sortable-table/sitemap.xml
Sitemap: https://samiodeh1337.github.io/react-table-dnd/sitemap.xml
8 changes: 8 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://samiodeh1337.github.io/react-table-dnd/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
} from './Components'
import './docs.css'

declare const __LIB_VERSION__: string

// Raw source imports for code preview (TSX — from real example files)
import srcFixedTsx from './examples/example-fixed.tsx?raw'
import srcFlexTsx from './examples/example-flex.tsx?raw'
Expand Down Expand Up @@ -497,7 +499,7 @@ function App() {

{/* Hero */}
<section className="hero">
<div className="badge">v2.0.8 &middot; MIT License</div>
<div className="badge">v{__LIB_VERSION__} &middot; MIT License</div>
<h1>
The drag-and-drop table
<br />
Expand Down
5 changes: 1 addition & 4 deletions src/Components/BodyRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Draggable from './Draggable'
import classNames from 'classnames'
import React, { useMemo, memo } from 'react'
import type { CSSProperties, ReactNode } from 'react'
import type { DraggableProps } from './Draggable'
Expand All @@ -22,11 +21,9 @@ const BodyRow: React.FC<BodyRowProps> = memo(({ children, style, className, ...p
() => (style ? { ...DEFAULT_STYLES, ...style } : DEFAULT_STYLES),
[style],
)
const mergedClassName = classNames('tr', className)

return (
<Draggable {...(props as DraggableProps)} type={'row'}>
<div className={mergedClassName} style={mergedStyles}>
<div data-rtdnd="tr" className={className} style={mergedStyles}>
{children}
</div>
</Draggable>
Expand Down
3 changes: 2 additions & 1 deletion src/Components/ColumnCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const ColumnCell: React.FC<ColumnCellProps> = memo(({ children, style, className
return (
<Draggable {...(props as DraggableProps)} styles={draggableStyles} type={'column'}>
<div
className={`th ${className ?? ''}`}
data-rtdnd="th"
className={className}
data-width={colCellWidth}
style={{ width: '100%', ...contentStyle, boxSizing: 'border-box' }}
>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Draggable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const Draggable: React.FC<DraggableProps> = memo(({ children, id, index, type, s
return (
<div
ref={outerRef}
className="draggable"
data-rtdnd="draggable"
data-id={id}
data-index={index}
data-type={type}
Expand Down
2 changes: 1 addition & 1 deletion src/Components/RowCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const RowCell: React.FC<RowCellProps> = memo(({ children, style, className, ...p
)

return (
<div className={`td ${className ?? ''}`} style={styles} data-col-index={index}>
<div data-rtdnd="td" className={className} style={styles} data-col-index={index}>
{children}
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions src/Components/TableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ const TableBody = forwardRef<HTMLDivElement, TableBodyProps>(
}, [dispatch, localRef])

return (
<div className={`body ${className ?? ''}`} style={BODY_STYLES}>
<div data-rtdnd="body" className={className} style={BODY_STYLES}>
<div
className="ibody"
data-rtdnd="ibody"
style={InnerBodyDefaultStyles}
data-droppableid={'body'}
onScroll={BodyScrollHandle}
Expand Down
3 changes: 2 additions & 1 deletion src/Components/TableContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ const TableProvider = forwardRef<HTMLDivElement, TableProviderProps>(
onMouseDown={dragStart}
onTouchStart={touchStart}
style={{ ...TABLE_DEFAULT_STYLES, ...style }}
className={`table ${className ?? ''}`}
data-rtdnd="table"
className={className}
>
{children}
</div>
Expand Down
16 changes: 9 additions & 7 deletions src/Components/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(
useLayoutEffect(() => {
const el = getRefCurrent(resolvedRef)
if (el) {
const widths: number[] = Array.from(el.querySelectorAll<HTMLElement>('.th')).map((th) => {
const widths: number[] = Array.from(
el.querySelectorAll<HTMLElement>('[data-rtdnd="th"]'),
).map((th) => {
const w = th.getAttribute('data-width')
return w ? parseInt(w, 10) : 0
})
Expand All @@ -75,23 +77,23 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(
useLayoutEffect(() => {
const el = getRefCurrent(resolvedRef)
if (el) {
const ids: string[] = Array.from(el.querySelectorAll<HTMLElement>('.draggable')).map(
(d) => d.getAttribute('data-id') || '',
)
const ids: string[] = Array.from(
el.querySelectorAll<HTMLElement>('[data-rtdnd="draggable"]'),
).map((d) => d.getAttribute('data-id') || '')
dispatch({ type: 'setColumnIds', value: ids })
}
}, [children, dispatch, getRefCurrent, resolvedRef])

return (
<div className={`header ${className ?? ''}`}>
<div data-rtdnd="header" className={className}>
<div
className="thead"
data-rtdnd="thead"
style={theadDefaultStyles}
data-droppableid={'header'}
onScroll={HeaderScrollHandle}
ref={resolvedRef}
>
<div style={defaultStyles} className="tr">
<div style={defaultStyles} data-rtdnd="tr">
{children}
</div>
</div>
Expand Down
31 changes: 19 additions & 12 deletions src/Components/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@
touch-action: none;
}

/* prevent native mobile long-press text selection on draggable elements */
[data-flexitable-root] [data-id] {
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}

/* structural reset: targets only the library's own elements,
never bleeds into consumer cell content */
[data-flexitable-root],
[data-flexitable-root] .table,
[data-flexitable-root] .header,
[data-flexitable-root] .thead,
[data-flexitable-root] .body,
[data-flexitable-root] .ibody,
[data-flexitable-root] .draggable,
[data-flexitable-root] .tr {
[data-flexitable-root] [data-rtdnd='table'],
[data-flexitable-root] [data-rtdnd='header'],
[data-flexitable-root] [data-rtdnd='thead'],
[data-flexitable-root] [data-rtdnd='body'],
[data-flexitable-root] [data-rtdnd='ibody'],
[data-flexitable-root] [data-rtdnd='draggable'],
[data-flexitable-root] [data-rtdnd='tr'] {
box-sizing: border-box;
margin: 0;
padding: 0;
Expand All @@ -29,8 +36,8 @@

/* user-facing cells: box-sizing only — padding/margin intentionally left
unset so consumers can style freely via className and style props */
[data-flexitable-root] .th,
[data-flexitable-root] .td {
[data-flexitable-root] [data-rtdnd='th'],
[data-flexitable-root] [data-rtdnd='td'] {
box-sizing: border-box;
}

Expand All @@ -45,17 +52,17 @@
}

/* hide webkit scrollbar on header scroll container (scrollLeft synced via JS) */
[data-flexitable-root] .thead::-webkit-scrollbar {
[data-flexitable-root] [data-rtdnd='thead']::-webkit-scrollbar {
display: none;
}

/* hide webkit scrollbar on virtual column drag body strip */
[data-flexitable-root] .clone-body-strip::-webkit-scrollbar {
[data-flexitable-root] [data-rtdnd='clone-body-strip']::-webkit-scrollbar {
display: none;
}

/* drag handle: only the handle shows grab cursor when not actively dragging */
[data-flexitable-root]:not(.is-dragging) .draggable:has([data-drag-handle]) > div {
[data-flexitable-root]:not(.is-dragging) [data-rtdnd='draggable']:has([data-drag-handle]) > div {
cursor: default !important;
}

Expand Down
8 changes: 4 additions & 4 deletions src/hooks/useDragContextEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ const useDragContextEvents = (
const idx = String(sourceIndex)

// Header: clone just the .th element — mirrors React.cloneElement(children) from Draggable
const thEl = draggableEl.querySelector('.th')
const thEl = draggableEl.querySelector('[data-rtdnd="th"]')
if (thEl) {
const headerWrapper = document.createElement('div')
headerWrapper.style.flexShrink = '0'
Expand All @@ -174,7 +174,7 @@ const useDragContextEvents = (
bodyWrapper.style.flex = '1'

const inner = document.createElement('div')
inner.className = 'rbody'
inner.dataset.rtdnd = 'rbody'
inner.style.height = `${body.scrollHeight}px`
inner.style.position = 'relative'

Expand All @@ -183,7 +183,7 @@ const useDragContextEvents = (
if (isVirtualRef.current) {
// Virtual rows have spacer divs alongside cells. Strip .tr down to just
// the target cell so the column strip shows correctly at offset 0.
const trEl = rowClone.querySelector('.tr')
const trEl = rowClone.querySelector('[data-rtdnd="tr"]')
if (trEl) {
const targetCell = trEl.querySelector(`[data-col-index="${idx}"]`)
while (trEl.firstChild) trEl.removeChild(trEl.firstChild)
Expand All @@ -204,7 +204,7 @@ const useDragContextEvents = (
// The 'clone-body-strip' class hides scrollbar in WebKit via style.css
bodyWrapper.style.overflow = 'auto'
bodyWrapper.style.scrollbarWidth = 'none'
bodyWrapper.className = 'clone-body-strip'
bodyWrapper.dataset.rtdnd = 'clone-body-strip'
} else {
bodyWrapper.style.overflow = 'hidden'
}
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useDropTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const useDropTarget = (refs: HookRefs, options: Options): DropTargetResult => {
const scrollTop = body.scrollTop
const topOffset = body.getBoundingClientRect().top

const elements = body.querySelectorAll('.draggable[data-type="row"]')
const elements = body.querySelectorAll('[data-rtdnd="draggable"][data-type="row"]')
let items: RowItem[] = []
for (let i = 0; i < elements.length; i++) {
const el = elements[i] as HTMLElement
Expand Down
Loading
Loading