From 2205e8daf1018808a415f8ba519448b63808a27f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Mar 2026 18:07:46 +0000
Subject: [PATCH 1/6] Initial plan
From 4801befc33ee324f5bf4ce114438e603185a5deb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Mar 2026 18:11:02 +0000
Subject: [PATCH 2/6] Add comprehensive Web Component conversion research
document
Co-authored-by: jebaird <280778+jebaird@users.noreply.github.com>
---
WEB_COMPONENT_CONVERSION_RESEARCH.md | 530 +++++++++++++++++++++++++++
1 file changed, 530 insertions(+)
create mode 100644 WEB_COMPONENT_CONVERSION_RESEARCH.md
diff --git a/WEB_COMPONENT_CONVERSION_RESEARCH.md b/WEB_COMPONENT_CONVERSION_RESEARCH.md
new file mode 100644
index 0000000..cc9a9ca
--- /dev/null
+++ b/WEB_COMPONENT_CONVERSION_RESEARCH.md
@@ -0,0 +1,530 @@
+# Web Component Conversion Research: Dragtable
+
+## Executive Summary
+
+This document outlines the research findings and recommendations for converting the dragtable jQuery UI widget into a native Web Component. The conversion is **feasible but represents a significant rewrite** rather than a simple port, as it involves moving from a jQuery plugin paradigm to native browser APIs.
+
+---
+
+## Current Architecture Analysis
+
+### Technology Stack (Current)
+- **jQuery** (1.7.2+) - DOM manipulation, event handling, `delegate()`, `bind()`, `unbind()`
+- **jQuery UI** (1.8.16+) - Widget factory (`$.widget()`)
+- **CSS** - Standard styling for drag visualization
+
+### Key Dependencies on jQuery/jQuery UI
+1. **Widget Factory Pattern** (`$.widget("jb.dragtable", {...})`)
+ - Automatic option management
+ - Lifecycle methods (`_create`, `_destroy`, `_setOption`)
+ - Event triggering (`_trigger`)
+ - Method chaining
+
+2. **jQuery Methods Used**
+ - `$.each()` - iteration
+ - `.delegate()` / `.bind()` / `.unbind()` - event delegation
+ - `.css()` - style manipulation
+ - `.addClass()` / `.removeClass()` - class manipulation
+ - `.find()` / `.filter()` / `.eq()` / `.closest()` - DOM traversal
+ - `.position()` / `.offset()` / `.outerWidth()` - geometry
+ - `.disableSelection()` / `.enableSelection()` - jQuery UI utilities
+ - `.appendTo()` / `.remove()` - DOM manipulation
+ - `.attr()` / `.data()` - attribute access
+
+3. **Widget Size**
+ - ~593 lines of JavaScript
+ - ~51 lines of CSS
+
+---
+
+## Web Component Conversion Approach
+
+### Recommended Approach: Custom Element with Shadow DOM
+
+```javascript
+class DragTable extends HTMLElement {
+ // Custom element implementation
+}
+customElements.define('drag-table', DragTable);
+```
+
+### Two Possible Strategies
+
+#### Strategy 1: Wrapper Component (Lower Effort)
+Create a Web Component that wraps an existing `
` element:
+
+```html
+
+
+
+```
+
+**Pros:**
+- Works with existing table markup
+- Easier to adopt incrementally
+- Light DOM means standard table accessibility
+
+**Cons:**
+- Cannot use Shadow DOM for style encapsulation
+- Must carefully manage slotted content
+
+#### Strategy 2: Full Encapsulation (Higher Effort, Better Architecture)
+Create a fully encapsulated component that renders its own table:
+
+```html
+
+```
+
+**Pros:**
+- Full Shadow DOM encapsulation
+- Complete control over rendering
+- Better for framework integration
+
+**Cons:**
+- Requires passing data via attributes/properties
+- More complex implementation
+- May need additional APIs for complex tables
+
+### Recommended: Strategy 1 (Wrapper Component)
+
+For backward compatibility and easier migration, the wrapper approach is recommended.
+
+---
+
+## Detailed Conversion Mapping
+
+### 1. Widget Factory → Custom Element Lifecycle
+
+| jQuery UI Widget | Web Component |
+|------------------|---------------|
+| `$.widget("jb.dragtable", {...})` | `class DragTable extends HTMLElement` |
+| `_create()` | `connectedCallback()` |
+| `_destroy()` | `disconnectedCallback()` |
+| `_setOption()` | `attributeChangedCallback()` + setters |
+| `this.element` | `this` or `this.querySelector('table')` |
+| `this.options` | `this.getAttribute()` or class properties |
+
+### 2. Event System Conversion
+
+| jQuery UI Events | Web Component Equivalent |
+|------------------|--------------------------|
+| `this._trigger('start', e, data)` | `this.dispatchEvent(new CustomEvent('dragtable-start', { detail: data, bubbles: true }))` |
+| `.delegate(selector, 'mousedown', fn)` | `addEventListener('mousedown', fn)` + manual delegation |
+| `.bind('mousemove', fn)` | `document.addEventListener('mousemove', fn)` |
+| `.unbind('mousemove')` | `document.removeEventListener('mousemove', fn)` |
+
+### 3. DOM Manipulation Conversion
+
+| jQuery | Native API |
+|--------|------------|
+| `$(selector)` | `document.querySelector(selector)` |
+| `$el.find(sel)` | `el.querySelectorAll(sel)` |
+| `$el.closest(sel)` | `el.closest(sel)` |
+| `$el.addClass('x')` | `el.classList.add('x')` |
+| `$el.removeClass('x')` | `el.classList.remove('x')` |
+| `$el.hasClass('x')` | `el.classList.contains('x')` |
+| `$el.css('prop', val)` | `el.style.prop = val` |
+| `$el.attr('name')` | `el.getAttribute('name')` |
+| `$el.position()` | `el.getBoundingClientRect()` (with adjustments) |
+| `$el.outerWidth()` | `el.offsetWidth` |
+| `$el.appendTo(target)` | `target.appendChild(el)` |
+| `$el.remove()` | `el.remove()` |
+| `$.each(arr, fn)` | `arr.forEach(fn)` |
+
+### 4. jQuery UI Specific Methods
+
+```javascript
+// jQuery UI disableSelection
+// Replace with CSS: user-select: none
+el.style.userSelect = 'none';
+
+// jQuery UI enableSelection
+el.style.userSelect = '';
+```
+
+---
+
+## Implementation Skeleton
+
+```javascript
+class DragTable extends HTMLElement {
+ static get observedAttributes() {
+ return ['data-header', 'handle', 'items', 'boundary', 'placeholder', 'scroll'];
+ }
+
+ constructor() {
+ super();
+
+ // Default options
+ this._options = {
+ dataHeader: 'data-header',
+ handle: 'dragtable-drag-handle',
+ items: 'th:not(:has(.dragtable-drag-handle)), .dragtable-drag-handle',
+ boundary: 'dragtable-drag-boundary',
+ placeholder: 'dragtable-col-placeholder',
+ scroll: false
+ };
+
+ // State
+ this._startIndex = null;
+ this._endIndex = null;
+ this._currentColumnCollection = [];
+ this._currentColumnCollectionOffset = {};
+ this._dragDisplay = null;
+ this._table = null;
+
+ // Bound handlers for proper cleanup
+ this._onMouseDown = this._handleMouseDown.bind(this);
+ this._onMouseMove = this._handleMouseMove.bind(this);
+ this._onMouseUp = this._handleMouseUp.bind(this);
+ }
+
+ connectedCallback() {
+ this._table = this.querySelector('table');
+ if (!this._table) {
+ console.warn('DragTable: No element found');
+ return;
+ }
+
+ // Inject styles
+ this._injectStyles();
+
+ // Set up event delegation
+ this._table.addEventListener('mousedown', this._onMouseDown);
+ }
+
+ disconnectedCallback() {
+ this._table?.removeEventListener('mousedown', this._onMouseDown);
+ document.removeEventListener('mousemove', this._onMouseMove);
+ document.removeEventListener('mouseup', this._onMouseUp);
+ this._cleanup();
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ const camelName = name.replace(/-([a-z])/g, g => g[1].toUpperCase());
+ this._options[camelName] = newValue === 'true' ? true :
+ newValue === 'false' ? false : newValue;
+ }
+
+ // Public API
+ order(newOrder) {
+ if (newOrder === undefined) {
+ return this._getOrder();
+ } else {
+ this._setOrder(newOrder);
+ return this;
+ }
+ }
+
+ // Private methods would follow...
+ _handleMouseDown(e) { /* ... */ }
+ _handleMouseMove(e) { /* ... */ }
+ _handleMouseUp(e) { /* ... */ }
+ _getCol(index) { /* ... */ }
+ _dropCol() { /* ... */ }
+ _swapCol(to) { /* ... */ }
+ _getCells(table, index) { /* ... */ }
+ _swapCells(a, b) { /* ... */ }
+ _getOrder() { /* ... */ }
+ _setOrder(order) { /* ... */ }
+ _emitEvent(name, detail) { /* ... */ }
+ _injectStyles() { /* ... */ }
+ _cleanup() { /* ... */ }
+}
+
+customElements.define('drag-table', DragTable);
+```
+
+---
+
+## CSS Strategy
+
+### Option A: Adoptable Stylesheets (Modern)
+```javascript
+const styles = new CSSStyleSheet();
+styles.replaceSync(`
+ .dragtable-drag-handle { cursor: move; }
+ .dragtable-drag-wrapper { position: absolute; z-index: 1000; }
+ /* ... */
+`);
+
+// In connectedCallback:
+document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
+```
+
+### Option B: Injected `
+
+
+ 🎯 DragTable Web Component Demo
+ Drag column headers to reorder columns. This is a native Web Component with no jQuery dependency!
+
+
+
+
Basic Example
+
Simply wrap your table with <drag-table>:
+
+
+
+
+
+ | Name |
+ Age |
+ City |
+ Country |
+
+
+
+
+ | John Doe |
+ 32 |
+ New York |
+ USA |
+
+
+ | Jane Smith |
+ 28 |
+ London |
+ UK |
+
+
+ | Bob Johnson |
+ 45 |
+ Sydney |
+ Australia |
+
+
+ | Alice Brown |
+ 36 |
+ Toronto |
+ Canada |
+
+
+
+
+
+
+ Current order:
+
+
+
+
+
+
+
+
+
Event Handling
+
Listen to drag events: dragtable-start, dragtable-change, dragtable-stop
+
+
+
+
+
+ | Product |
+ Price |
+ Quantity |
+ Total |
+
+
+
+
+ | Widget A |
+ $10.00 |
+ 5 |
+ $50.00 |
+
+
+ | Widget B |
+ $15.00 |
+ 3 |
+ $45.00 |
+
+
+ | Widget C |
+ $8.00 |
+ 10 |
+ $80.00 |
+
+
+
+
+
+
Event Log:
+
+
+
+
+
+
+
Usage
+
+
HTML
+
<script type="module" src="dragtable.js"></script>
+
+<drag-table>
+ <table>
+ <thead>
+ <tr>
+ <th data-header="name">Name</th>
+ <th data-header="age">Age</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr><td>John</td><td>32</td></tr>
+ </tbody>
+ </table>
+</drag-table>
+
+
JavaScript API
+
const dragTable = document.querySelector('drag-table');
+
+// Get current column order
+const order = dragTable.order();
+console.log(order); // ['name', 'age']
+
+// Set column order
+dragTable.order(['age', 'name']);
+
+// Listen to events
+dragTable.addEventListener('dragtable-change', (e) => {
+ console.log('Column moved:', e.detail);
+});
+
+
Available Events
+
dragtable-start // When drag begins
+dragtable-beforechange // Before column swap (cancelable)
+dragtable-change // After column swap
+dragtable-stop // When drag ends
+
+
+
+
+
+
+
+