Sign In

@erplora/outfitkit

Package Overview
Dependencies
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@erplora/outfitkit - npm Package Compare versions

Comparing version
0.1.39
to
0.1.40
+1
dist/components/ok-kanban/ok-kanban.test.d.ts
import './ok-kanban';
+15
-5

@@ -39,9 +39,18 @@ import { LitElement } from 'lit';

private lastColumnsRef;
private pointerDrag;
private suppressClick;
private suppressClickTimer;
private syncFromProp;
private onDragStart;
private onDragEnd;
private onColumnDragOver;
private onCardDragOver;
private onColumnDrop;
private onCardDrop;
private capturePointer;
private releasePointer;
private onPointerDown;
/** Elemento real bajo el puntero capturado, dentro del shadow root. */
private elementFromPoint;
/** Actualiza columna y posición de inserción a partir de las coordenadas del puntero. */
private updateDropTarget;
private onPointerMove;
private suppressNextCardClick;
private onPointerUp;
private onPointerCancel;
private commitMove;

@@ -52,2 +61,3 @@ private onCardClick;

render(): unknown;
disconnectedCallback(): void;
}

@@ -54,0 +64,0 @@ declare global {

@@ -16,2 +16,3 @@ import { LitElement, css, html } from "lit";

};
const DRAG_THRESHOLD_PX = 5;
class OkKanban extends LitElement {

@@ -28,2 +29,5 @@ constructor() {

this.lastColumnsRef = null;
this.pointerDrag = null;
this.suppressClick = false;
this.suppressClickTimer = null;
}

@@ -170,2 +174,5 @@ static {

user-select: none;
position: relative;
padding-inline-end: 3.25rem;
-webkit-user-drag: none;
transition: background-color var(--ok-transition, 150ms ease),

@@ -192,2 +199,23 @@ color var(--ok-transition, 150ms ease), border-color var(--ok-transition, 150ms ease),

}
/* El agarre separa las dos intenciones táctiles: deslizar sobre la tarjeta conserva el scroll
natural del tablero; deslizar desde aquí mueve la tarjeta. Solo este target bloquea el pan
nativo, de modo que el kanban sigue siendo navegable cuando una columna está llena. */
.drag-handle {
position: absolute;
inset-block-start: 0.15rem;
inset-inline-end: 0.1rem;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
color: var(--color-muted);
font-size: 1.15rem;
line-height: 1;
cursor: grab;
touch-action: none;
}
.drag-handle:active {
cursor: grabbing;
}
@media (prefers-reduced-motion: reduce) {

@@ -248,10 +276,3 @@ .card:not(.dragging):hover,

}
// ---- Drag & drop nativo HTML5 ---------------------------------------------------------------
onDragStart(e, fromColumn, cardId) {
this.drag = { cardId, fromColumn };
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", cardId);
}
}
// ---- Drag & drop con Pointer Events ---------------------------------------------------------
onDragEnd() {

@@ -263,40 +284,100 @@ this.drag = null;

}
// Mientras se arrastra sobre una columna: permite soltar y resalta la columna.
onColumnDragOver(e, columnId) {
if (!this.drag) return;
e.preventDefault();
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
if (this.overColumn !== columnId) this.overColumn = columnId;
this.overCardId = null;
this.overAfter = false;
capturePointer(target, pointerId) {
try {
target.setPointerCapture?.(pointerId);
} catch {
}
}
// Mientras se arrastra sobre una tarjeta: calcula si la inserción va antes o después según
// la posición vertical del puntero respecto al centro de la tarjeta.
onCardDragOver(e, columnId, cardId) {
if (!this.drag) return;
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
const target = e.currentTarget;
const rect = target.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
if (this.overColumn !== columnId) this.overColumn = columnId;
if (this.overCardId !== cardId || this.overAfter !== after) {
this.overCardId = cardId;
this.overAfter = after;
releasePointer(state2) {
try {
if (state2.captureTarget.hasPointerCapture?.(state2.pointerId)) {
state2.captureTarget.releasePointerCapture?.(state2.pointerId);
}
} catch {
}
}
// Suelta sobre la columna (zona vacía o tras propagación): inserta al final si no hay tarjeta diana.
onColumnDrop(e, toColumn) {
if (!this.drag) return;
onPointerDown(e, fromColumn, cardId) {
if (this.pointerDrag || e.button !== 0) return;
if (e.pointerType === "touch" && !(e.target instanceof Element && e.target.closest(".drag-handle"))) {
return;
}
const captureTarget = e.currentTarget;
this.pointerDrag = {
pointerId: e.pointerId,
startX: e.clientX,
startY: e.clientY,
active: false,
captureTarget,
cardId,
fromColumn
};
this.capturePointer(captureTarget, e.pointerId);
}
/** Elemento real bajo el puntero capturado, dentro del shadow root. */
elementFromPoint(x, y) {
const root = this.renderRoot;
return root.elementFromPoint?.(x, y) ?? null;
}
/** Actualiza columna y posición de inserción a partir de las coordenadas del puntero. */
updateDropTarget(e) {
const hit = this.elementFromPoint(e.clientX, e.clientY);
const column = hit?.closest("[data-column-id]") ?? null;
const columnId = column?.dataset.columnId;
if (!column || !columnId || !this.renderRoot.contains(column)) {
this.overColumn = null;
this.overCardId = null;
this.overAfter = false;
return;
}
this.overColumn = columnId;
const targetCard = hit?.closest("[data-card-id]") ?? null;
if (!targetCard || targetCard.closest("[data-column-id]") !== column) {
this.overCardId = null;
this.overAfter = false;
return;
}
const rect = targetCard.getBoundingClientRect();
this.overCardId = targetCard.dataset.cardId ?? null;
this.overAfter = e.clientY > rect.top + rect.height / 2;
}
onPointerMove(e) {
const state2 = this.pointerDrag;
if (!state2 || state2.pointerId !== e.pointerId) return;
if (!state2.active) {
const distance = Math.hypot(e.clientX - state2.startX, e.clientY - state2.startY);
if (distance < DRAG_THRESHOLD_PX) return;
state2.active = true;
this.drag = { cardId: state2.cardId, fromColumn: state2.fromColumn };
}
e.preventDefault();
this.commitMove(toColumn);
this.updateDropTarget(e);
}
// Suelta sobre una tarjeta concreta: inserta antes/después de ella.
onCardDrop(e, toColumn) {
if (!this.drag) return;
suppressNextCardClick() {
this.suppressClick = true;
if (this.suppressClickTimer) clearTimeout(this.suppressClickTimer);
this.suppressClickTimer = setTimeout(() => {
this.suppressClick = false;
this.suppressClickTimer = null;
}, 0);
}
onPointerUp(e) {
const state2 = this.pointerDrag;
if (!state2 || state2.pointerId !== e.pointerId) return;
this.releasePointer(state2);
this.pointerDrag = null;
if (!state2.active) return;
e.preventDefault();
e.stopPropagation();
this.commitMove(toColumn);
this.updateDropTarget(e);
const toColumn = this.overColumn;
this.suppressNextCardClick();
if (toColumn) this.commitMove(toColumn);
else this.onDragEnd();
}
onPointerCancel(e) {
const state2 = this.pointerDrag;
if (!state2 || state2.pointerId !== e.pointerId) return;
this.releasePointer(state2);
this.pointerDrag = null;
this.onDragEnd();
}
// Aplica el movimiento en el modelo interno y emite `ok-card-move`.

@@ -346,2 +427,8 @@ commitMove(toColumn) {

onCardClick(card) {
if (this.suppressClick) {
this.suppressClick = false;
if (this.suppressClickTimer) clearTimeout(this.suppressClickTimer);
this.suppressClickTimer = null;
return;
}
this.dispatchEvent(

@@ -367,9 +454,11 @@ new CustomEvent("ok-card-click", {

class=${classes}
draggable="true"
@dragstart=${(e) => this.onDragStart(e, column.id, card.id)}
@dragend=${() => this.onDragEnd()}
@dragover=${(e) => this.onCardDragOver(e, column.id, card.id)}
@drop=${(e) => this.onCardDrop(e, column.id)}
data-card-id=${card.id}
@pointerdown=${(e) => this.onPointerDown(e, column.id, card.id)}
@click=${() => this.onCardClick(card)}
>
<span
class="drag-handle"
aria-hidden="true"
@click=${(e) => e.stopPropagation()}
>⠿</span>
<div class="card-title">${card.title}</div>

@@ -387,4 +476,3 @@ ${card.subtitle ? html`<div class="card-subtitle">${card.subtitle}</div>` : ""}

class=${`column ${over ? "drag-over" : ""}`.trim()}
@dragover=${(e) => this.onColumnDragOver(e, column.id)}
@drop=${(e) => this.onColumnDrop(e, column.id)}
data-column-id=${column.id}
>

@@ -407,6 +495,18 @@ <header class="column-header">

}
return html`<div class="board">
return html`<div
class="board"
@pointermove=${this.onPointerMove}
@pointerup=${this.onPointerUp}
@pointercancel=${this.onPointerCancel}
>
${this.view.map((column) => this.renderColumn(column))}
</div>`;
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.pointerDrag) this.releasePointer(this.pointerDrag);
this.pointerDrag = null;
if (this.suppressClickTimer) clearTimeout(this.suppressClickTimer);
this.suppressClickTimer = null;
}
}

@@ -413,0 +513,0 @@ __decorateClass([

{
"name": "@erplora/outfitkit",
"version": "0.1.39",
"version": "0.1.40",
"description": "OutfitKit — librería de Web Components (Lit) que CONSTRUYE lo que Ionic no tiene (tree, data-table rica, inline-feedback, kpi/stat, stepper/wizard, calendar, kanban…) sobre primitivos de Ionic. Ionic es la base; OutfitKit cubre los huecos. npm + CDN, imports individuales, CSP-safe. Tema vía tokens --ok-* (fallback a --ion-*).",

@@ -5,0 +5,0 @@ "type": "module",

@@ -155,3 +155,3 @@ # OutfitKit (`@erplora/outfitkit`)

| `ok-scheduler` | Timeline de recursos/turnos (filas × franjas horarias) con eventos posicionados. | `ok-event-click`, `ok-slot-click`, `ok-nav` |
| `ok-kanban` | Tablero Kanban con HTML5 drag&drop (sin libs), columnas y tarjetas reordenables. | `ok-card-move`, `ok-card-click` |
| `ok-kanban` | Tablero Kanban con Pointer Events (ratón, lápiz y agarre táctil; sin libs), columnas y tarjetas reordenables. | `ok-card-move`, `ok-card-click` |
| `ok-timeline` | Línea de tiempo vertical con estados (done/current/pending), modo alternado. | `ok-item-click` |

@@ -158,0 +158,0 @@

Sorry, the diff of this file is too big to display