@erplora/outfitkit
Advanced tools
@@ -14,2 +14,4 @@ import { LitElement } from 'lit'; | ||
| children?: OkFmFolder[]; | ||
| /** Solo lectura (carpeta reservada del hub o de un módulo): no se arrastra ni recibe drops. */ | ||
| readOnly?: boolean; | ||
| } | ||
@@ -132,3 +134,5 @@ /** Archivo del contenido de la carpeta actual. Lo aporta el host vía `.files`. */ | ||
| private dragging; | ||
| private dropTarget; | ||
| private seeded; | ||
| private static readonly MOVE_MIME; | ||
| private fileInput; | ||
@@ -154,5 +158,12 @@ private searchTimer?; | ||
| private toggle; | ||
| private isMoveDrag; | ||
| private onItemDragStart; | ||
| private onDragOver; | ||
| private onDragLeave; | ||
| private onDrop; | ||
| private onFolderDragOver; | ||
| private onFolderDragLeave; | ||
| private onFolderDrop; | ||
| /** Decodifica el payload de movimiento del evento y emite `ok-move` hacia `to`. */ | ||
| private emitMoveIfAny; | ||
| private extOf; | ||
@@ -159,0 +170,0 @@ private variant; |
+110
-27
@@ -30,3 +30,3 @@ import { LitElement, css, html } from "lit"; | ||
| }; | ||
| class OkFileManager extends LitElement { | ||
| const _OkFileManager = class _OkFileManager2 extends LitElement { | ||
| constructor() { | ||
@@ -46,2 +46,3 @@ super(...arguments); | ||
| this.dragging = false; | ||
| this.dropTarget = null; | ||
| this.seeded = false; | ||
@@ -145,2 +146,8 @@ } | ||
| } | ||
| /* Carpeta que recibe un drop mientras se arrastra sobre ella. */ | ||
| .trow.drop { | ||
| background: color-mix(in srgb, var(--brand) 10%, transparent); | ||
| outline: 2px dashed var(--brand); | ||
| outline-offset: -2px; | ||
| } | ||
| .caret { | ||
@@ -665,2 +672,5 @@ flex: 0 0 auto; | ||
| } | ||
| static { | ||
| this.MOVE_MIME = "application/x-ok-file-manager-move"; | ||
| } | ||
| // Siembra: expande todas las carpetas con hijos la primera vez. | ||
@@ -737,20 +747,76 @@ seedExpanded(folders) { | ||
| } | ||
| // ---- Drag & drop sobre el main ---- | ||
| // ---- Drag & drop ---- | ||
| // | ||
| // Hay DOS flujos: | ||
| // 1. Subida: arrastrar ficheros del SO sobre el área main → `ok-upload`. | ||
| // 2. Reubicación: arrastrar un fichero o carpeta del gestor a otra carpeta (o a la raíz) → | ||
| // `ok-move`. Las carpetas de solo lectura nunca se arrastran ni reciben drops; el backend | ||
| // revalida, así que esto es solo UX (evita ofrecer un movimiento que dará 403). | ||
| isMoveDrag(e) { | ||
| return !!e.dataTransfer?.types.includes(_OkFileManager2.MOVE_MIME); | ||
| } | ||
| // Comienza a arrastrar un fichero o carpeta del gestor. Las carpetas readOnly no se arrastran: | ||
| // el atributo `draggable` de su fila lo impide, pero esto es un cinturón extra. | ||
| onItemDragStart(e, id, kind) { | ||
| if (!e.dataTransfer) return; | ||
| e.dataTransfer.setData(_OkFileManager2.MOVE_MIME, JSON.stringify({ id, kind })); | ||
| e.dataTransfer.effectAllowed = "move"; | ||
| } | ||
| // Soltar sobre el área main equivale a soltar en la raíz (`''`). | ||
| onDragOver(e) { | ||
| if (!this.uploadable) return; | ||
| e.preventDefault(); | ||
| this.dragging = true; | ||
| const externalFiles = !!e.dataTransfer?.types.includes("Files"); | ||
| if (externalFiles) { | ||
| if (!this.uploadable || !this.can("upload")) return; | ||
| e.preventDefault(); | ||
| this.dragging = true; | ||
| return; | ||
| } | ||
| if (this.isMoveDrag(e)) { | ||
| e.preventDefault(); | ||
| this.dragging = true; | ||
| } | ||
| } | ||
| onDragLeave(e) { | ||
| if (!this.uploadable) return; | ||
| e.preventDefault(); | ||
| this.dragging = false; | ||
| if (!e.currentTarget.contains(e.relatedTarget)) { | ||
| this.dragging = false; | ||
| } | ||
| } | ||
| onDrop(e) { | ||
| if (!this.uploadable) return; | ||
| e.preventDefault(); | ||
| this.dragging = false; | ||
| const files = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : []; | ||
| if (files.length) this.emit("ok-upload", { files }); | ||
| if (files.length) { | ||
| if (this.uploadable && this.can("upload")) this.emit("ok-upload", { files }); | ||
| return; | ||
| } | ||
| this.emitMoveIfAny(e, ""); | ||
| } | ||
| // ---- Drag & drop sobre filas de carpeta del árbol ---- | ||
| onFolderDragOver(e, folder) { | ||
| if (!this.isMoveDrag(e)) return; | ||
| if (folder.readOnly) return; | ||
| e.preventDefault(); | ||
| this.dropTarget = folder.id; | ||
| e.dataTransfer.dropEffect = "move"; | ||
| } | ||
| onFolderDragLeave(folder) { | ||
| if (this.dropTarget === folder.id) this.dropTarget = null; | ||
| } | ||
| onFolderDrop(e, folder) { | ||
| if (!this.isMoveDrag(e) || folder.readOnly) return; | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| this.dropTarget = null; | ||
| this.emitMoveIfAny(e, folder.id); | ||
| } | ||
| /** Decodifica el payload de movimiento del evento y emite `ok-move` hacia `to`. */ | ||
| emitMoveIfAny(e, to) { | ||
| const raw = e.dataTransfer?.getData(_OkFileManager2.MOVE_MIME); | ||
| if (!raw) return; | ||
| try { | ||
| const { id } = JSON.parse(raw); | ||
| if (id) this.emit("ok-move", { from: id, to }); | ||
| } catch { | ||
| } | ||
| } | ||
| // Deriva la extensión: usa `ext` o la cola del nombre. | ||
@@ -791,4 +857,13 @@ extOf(file) { | ||
| const active = folder.id === this.selected; | ||
| const over = this.dropTarget === folder.id; | ||
| return html`<li role="treeitem" aria-selected=${active ? "true" : "false"} aria-expanded=${hasChildren ? String(expanded) : ""}> | ||
| <div class=${`trow ${active ? "active" : ""}`.trim()} @click=${() => this.navigate(folder.id)}> | ||
| <div | ||
| class=${`trow ${active ? "active" : ""} ${over ? "drop" : ""}`.trim()} | ||
| draggable=${folder.readOnly ? "false" : "true"} | ||
| @click=${() => this.navigate(folder.id)} | ||
| @dragstart=${(e) => this.onItemDragStart(e, folder.id, "folder")} | ||
| @dragover=${(e) => this.onFolderDragOver(e, folder)} | ||
| @dragleave=${() => this.onFolderDragLeave(folder)} | ||
| @drop=${(e) => this.onFolderDrop(e, folder)} | ||
| > | ||
| <button | ||
@@ -1067,2 +1142,3 @@ type="button" | ||
| tabindex="0" | ||
| draggable="true" | ||
| @dblclick=${() => this.open(file.id)} | ||
@@ -1072,2 +1148,3 @@ @keydown=${(e) => { | ||
| }} | ||
| @dragstart=${(e) => this.onItemDragStart(e, file.id, "file")} | ||
| > | ||
@@ -1091,2 +1168,3 @@ <div class="card-actions">${this.fileActions(file)}</div> | ||
| tabindex="0" | ||
| draggable="true" | ||
| @dblclick=${() => this.open(file.id)} | ||
@@ -1096,2 +1174,3 @@ @keydown=${(e) => { | ||
| }} | ||
| @dragstart=${(e) => this.onItemDragStart(e, file.id, "file")} | ||
| > | ||
@@ -1153,48 +1232,52 @@ ${this.renderBadge(file)} | ||
| } | ||
| } | ||
| }; | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "folders"); | ||
| ], _OkFileManager.prototype, "folders"); | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "files"); | ||
| ], _OkFileManager.prototype, "files"); | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "path"); | ||
| ], _OkFileManager.prototype, "path"); | ||
| __decorateClass([ | ||
| property() | ||
| ], OkFileManager.prototype, "selected"); | ||
| ], _OkFileManager.prototype, "selected"); | ||
| __decorateClass([ | ||
| property() | ||
| ], OkFileManager.prototype, "view"); | ||
| ], _OkFileManager.prototype, "view"); | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "quota"); | ||
| ], _OkFileManager.prototype, "quota"); | ||
| __decorateClass([ | ||
| property() | ||
| ], OkFileManager.prototype, "title"); | ||
| ], _OkFileManager.prototype, "title"); | ||
| __decorateClass([ | ||
| property({ type: Boolean }) | ||
| ], OkFileManager.prototype, "searchable"); | ||
| ], _OkFileManager.prototype, "searchable"); | ||
| __decorateClass([ | ||
| property({ type: Boolean }) | ||
| ], OkFileManager.prototype, "uploadable"); | ||
| ], _OkFileManager.prototype, "uploadable"); | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "policy"); | ||
| ], _OkFileManager.prototype, "policy"); | ||
| __decorateClass([ | ||
| property({ type: Boolean }) | ||
| ], OkFileManager.prototype, "loading"); | ||
| ], _OkFileManager.prototype, "loading"); | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "labels"); | ||
| ], _OkFileManager.prototype, "labels"); | ||
| __decorateClass([ | ||
| state() | ||
| ], OkFileManager.prototype, "expandedIds"); | ||
| ], _OkFileManager.prototype, "expandedIds"); | ||
| __decorateClass([ | ||
| state() | ||
| ], OkFileManager.prototype, "dragging"); | ||
| ], _OkFileManager.prototype, "dragging"); | ||
| __decorateClass([ | ||
| state() | ||
| ], _OkFileManager.prototype, "dropTarget"); | ||
| __decorateClass([ | ||
| query('input[type="file"]') | ||
| ], OkFileManager.prototype, "fileInput"); | ||
| ], _OkFileManager.prototype, "fileInput"); | ||
| let OkFileManager = _OkFileManager; | ||
| define("ok-file-manager", OkFileManager); | ||
@@ -1201,0 +1284,0 @@ export { |
+1
-1
| { | ||
| "name": "@erplora/outfitkit", | ||
| "version": "0.1.33", | ||
| "version": "0.1.34", | ||
| "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", |
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
2112896
0.31%61048
0.27%