fix: make inline editor overlay follow scrolling and refine menu markup By: AICoder
This commit is contained in:
parent
ba7fccd6a7
commit
549ab0b552
104
dist/datatable.js
vendored
104
dist/datatable.js
vendored
@ -276,14 +276,14 @@
|
|||||||
};
|
};
|
||||||
const getSelectionBounds = () => {
|
const getSelectionBounds = () => {
|
||||||
if (!activeBounds) return null;
|
if (!activeBounds) return null;
|
||||||
let minRow = activeBounds.minRow, maxRow = activeBounds.maxRow, minCol = activeBounds.minCol, maxCol = activeBounds.maxCol;
|
let minRow2 = activeBounds.minRow, maxRow = activeBounds.maxRow, minCol2 = activeBounds.minCol, maxCol = activeBounds.maxCol;
|
||||||
multiSelections.forEach((s) => {
|
multiSelections.forEach((s) => {
|
||||||
minRow = Math.min(minRow, s.minRow);
|
minRow2 = Math.min(minRow2, s.minRow);
|
||||||
maxRow = Math.max(maxRow, s.maxRow);
|
maxRow = Math.max(maxRow, s.maxRow);
|
||||||
minCol = Math.min(minCol, s.minCol);
|
minCol2 = Math.min(minCol2, s.minCol);
|
||||||
maxCol = Math.max(maxCol, s.maxCol);
|
maxCol = Math.max(maxCol, s.maxCol);
|
||||||
});
|
});
|
||||||
return { minRow, maxRow, minCol, maxCol };
|
return { minRow: minRow2, maxRow, minCol: minCol2, maxCol };
|
||||||
};
|
};
|
||||||
const copy = async () => {
|
const copy = async () => {
|
||||||
const bounds = getSelectionBounds();
|
const bounds = getSelectionBounds();
|
||||||
@ -321,7 +321,11 @@
|
|||||||
cells.push(current);
|
cells.push(current);
|
||||||
return cells;
|
return cells;
|
||||||
});
|
});
|
||||||
const { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
let { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
||||||
|
if (minRow === maxRow && minCol === maxCol) {
|
||||||
|
maxRow = Infinity;
|
||||||
|
maxCol = Infinity;
|
||||||
|
}
|
||||||
const body = container.querySelector(".dt-body");
|
const body = container.querySelector(".dt-body");
|
||||||
const rowNodes = body ? Array.from(body.childNodes).filter((n) => {
|
const rowNodes = body ? Array.from(body.childNodes).filter((n) => {
|
||||||
var _a;
|
var _a;
|
||||||
@ -615,22 +619,30 @@
|
|||||||
});
|
});
|
||||||
container.editCell = (row, field, cellNode) => {
|
container.editCell = (row, field, cellNode) => {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const overlay = container.querySelector(".dt-editor-overlay"), rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect();
|
const main = container.querySelector(".dt-main");
|
||||||
|
const overlay = container.querySelector(".dt-editor-overlay"), rect = cellNode.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
|
||||||
currentEditingNode = cellNode;
|
currentEditingNode = cellNode;
|
||||||
const formType = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text";
|
const formType = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text";
|
||||||
const form = overlay.querySelector("AutoForm");
|
const form = overlay.querySelector("AutoForm");
|
||||||
if (form) {
|
if (form) {
|
||||||
form.data = row;
|
form.data = globalThis.NewState(globalThis.Util.clone(row));
|
||||||
form.state.schema = [{ ...field, type: formType, options: ((_b = field.settings) == null ? void 0 : _b.options) || field.options, name: field.id, label: "" }];
|
form.state.schema = [{ ...field, type: formType, options: ((_b = field.settings) == null ? void 0 : _b.options) || field.options, name: field.id, label: "" }];
|
||||||
}
|
}
|
||||||
const isComplex = ["textarea", "TagsInput", "checkbox", "radio"].includes(formType);
|
const isComplex = ["textarea", "TagsInput", "checkbox", "radio"].includes(formType);
|
||||||
|
let topPos = rect.top - mainRect.top + main.scrollTop - 1;
|
||||||
|
let leftPos = rect.left - mainRect.left + main.scrollLeft - 1;
|
||||||
|
let editorWidth = Math.max(rect.width + 2, isComplex ? 300 : 0);
|
||||||
|
const maxLeft = main.scrollWidth - editorWidth - 5;
|
||||||
|
if (leftPos > maxLeft) leftPos = Math.max(0, maxLeft);
|
||||||
Object.assign(overlay.style, {
|
Object.assign(overlay.style, {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
left: rect.left - rootRect.left - 1 + "px",
|
left: leftPos + "px",
|
||||||
top: rect.top - rootRect.top - 1 + "px",
|
top: topPos + "px",
|
||||||
width: Math.max(rect.width + 2, isComplex ? 300 : 0) + "px",
|
width: editorWidth + "px",
|
||||||
height: "auto",
|
height: "auto",
|
||||||
minHeight: rect.height + 2 + "px",
|
minHeight: rect.height + 2 + "px",
|
||||||
|
maxHeight: Math.max(100, mainRect.height - (rect.top - mainRect.top) - 5) + "px",
|
||||||
|
overflow: "auto",
|
||||||
padding: "0"
|
padding: "0"
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -645,22 +657,31 @@
|
|||||||
if (save && form && form.data) {
|
if (save && form && form.data) {
|
||||||
const input = _editorOverlay.querySelector("input:focus, select:focus, textarea:focus");
|
const input = _editorOverlay.querySelector("input:focus, select:focus, textarea:focus");
|
||||||
if (input) input.dispatchEvent(new Event(input.type === "number" || input.tagName === "SELECT" ? "change" : "input", { bubbles: true }));
|
if (input) input.dispatchEvent(new Event(input.type === "number" || input.tagName === "SELECT" ? "change" : "input", { bubbles: true }));
|
||||||
|
let hasChanges = false;
|
||||||
const schema = form.state.schema || [];
|
const schema = form.state.schema || [];
|
||||||
schema.forEach((field) => {
|
schema.forEach((field) => {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const row = (_b = (_a = currentEditingNode == null ? void 0 : currentEditingNode.closest(".dt-row")) == null ? void 0 : _a._ref) == null ? void 0 : _b.item;
|
const row = (_b = (_a = currentEditingNode == null ? void 0 : currentEditingNode.closest(".dt-row")) == null ? void 0 : _a._ref) == null ? void 0 : _b.item;
|
||||||
if (row) row[field.name] = form.data[field.name];
|
if (row && JSON.stringify(row[field.name]) !== JSON.stringify(form.data[field.name])) {
|
||||||
|
row[field.name] = form.data[field.name];
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (state.isBulkEdit) {
|
if (state.isBulkEdit) {
|
||||||
const { minRow, maxRow, fIdx } = state.isBulkEdit;
|
const { minRow: minRow2, maxRow, fIdx } = state.isBulkEdit;
|
||||||
const field = state.fields[fIdx];
|
const field = state.fields[fIdx];
|
||||||
const newValue = form.data[field.id];
|
const newValue = form.data[field.id];
|
||||||
for (let i = minRow; i <= maxRow; i++) {
|
for (let i = minRow2; i <= maxRow; i++) {
|
||||||
if (state.list[i]) state.list[i][field.id] = newValue;
|
if (state.list[i] && state.list[i][field.id] !== newValue) {
|
||||||
|
state.list[i][field.id] = newValue;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.list = [...state.list];
|
if (hasChanges) {
|
||||||
state.isDirty = true;
|
state.list = [...state.list];
|
||||||
|
state.isDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_editorOverlay.style.display = "none";
|
_editorOverlay.style.display = "none";
|
||||||
if (form) {
|
if (form) {
|
||||||
@ -851,7 +872,7 @@
|
|||||||
}, globalThis.Util.makeDom(
|
}, globalThis.Util.makeDom(
|
||||||
/*html*/
|
/*html*/
|
||||||
`
|
`
|
||||||
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-hidden" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-visible" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
||||||
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
||||||
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
||||||
style="overflow-anchor:none; min-height: 0">
|
style="overflow-anchor:none; min-height: 0">
|
||||||
@ -879,6 +900,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
||||||
@ -888,17 +910,21 @@
|
|||||||
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div $if="this.state?.activeModes?.length" class="dt-filter-tabs d-flex overflow-auto border-bottom bg-light-subtle rounded-top py-1" style="white-space:nowrap; scrollbar-width: none;">
|
|
||||||
<template $each="this.state?.activeModes || []" as="m">
|
|
||||||
<div $class="px-2 py-1 cursor-pointer fs-5 \${this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === m ? 'text-primary border-bottom border-primary border-2' : 'text-muted'}" $title="m.toUpperCase()" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
|
||||||
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'}"></i>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template $if="this.state?.activeModes?.length">
|
<template $if="this.state?.activeModes?.length">
|
||||||
<div class="py-2 border-bottom" style="min-height: 48px">
|
<div class="py-2 border-bottom" style="min-height: 48px">
|
||||||
<input type="text" class="form-control form-control-sm mb-1" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<div class="input-group input-group-sm mb-1">
|
||||||
|
<input type="text" class="form-control" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
|
<button class="btn btn-outline-secondary border dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Search Mode">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[this.state?.filterConfig?.[this.state?.activeFieldId]?.mode] || 'bi-filter'}"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||||
|
<template $each="this.state?.activeModes || []" as="m">
|
||||||
|
<li><button class="dropdown-item d-flex align-items-center" type="button" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'} me-2"></i> <span $text="m.toUpperCase()"></span>
|
||||||
|
</button></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -918,16 +944,14 @@
|
|||||||
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 pt-2 border-top d-flex flex-column gap-1">
|
<div class="mt-3 pt-2 border-top d-flex gap-1 justify-content-between">
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.editField()"><i class="bi bi-pencil me-2"></i> Edit Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Edit Field" $onclick="this.editField()"><i class="bi bi-pencil"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.addField()"><i class="bi bi-plus-lg me-2"></i> Add Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Add Field" $onclick="this.addField()"><i class="bi bi-plus-lg"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-danger border d-flex align-items-center px-2 py-1" $onclick="this.deleteField()"><i class="bi bi-trash me-2"></i> Delete Field</button>
|
<button class="btn btn-sm btn-outline-danger flex-grow-1" title="Delete Field" $onclick="this.deleteField()"><i class="bi bi-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
|
||||||
|
|
||||||
<Modal $.id="this.id + '_field_modal'">
|
<Modal $.id="this.id + '_field_modal'">
|
||||||
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
@ -938,9 +962,9 @@
|
|||||||
|
|
||||||
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
||||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||||
<div class="btn-group shadow-sm">
|
<div class="d-flex align-items-center gap-1">
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
||||||
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
||||||
@ -949,8 +973,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button $if="this.state?.isDirty" class="btn btn-xs btn-primary px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save Changes</button>
|
<button $if="this.state?.isDirty" class="btn btn-sm btn-primary border-0 px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save</button>
|
||||||
<button $if="!this.state?.isDirty" class="btn btn-xs btn-light border px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Up to date</button>
|
<button $if="!this.state?.isDirty" class="btn btn-sm btn-light border-0 px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Saved</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -975,9 +999,11 @@
|
|||||||
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
||||||
.menu-item-row .only-btn { opacity: 0; }
|
.menu-item-row .only-btn { opacity: 0; }
|
||||||
.menu-item-row:hover .only-btn { opacity: 1; }
|
.menu-item-row:hover .only-btn { opacity: 1; }
|
||||||
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; }
|
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; height: 100%; }
|
||||||
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
||||||
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0.375rem 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > .form-switch { padding-left: 2.5rem !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > textarea { min-height: 100px; resize: vertical; }
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
));
|
));
|
||||||
|
|||||||
2
dist/datatable.min.js
vendored
2
dist/datatable.min.js
vendored
File diff suppressed because one or more lines are too long
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/datatable",
|
"name": "@web/datatable",
|
||||||
"version": "1.0.12",
|
"version": "1.0.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@web/datatable",
|
"name": "@web/datatable",
|
||||||
"version": "1.0.12",
|
"version": "1.0.13",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.40.0",
|
"@playwright/test": "^1.40.0",
|
||||||
"@rollup/plugin-terser": "^1.0.0",
|
"@rollup/plugin-terser": "^1.0.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/datatable",
|
"name": "@web/datatable",
|
||||||
"version": "1.0.12",
|
"version": "1.0.13",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/datatable.js",
|
"main": "dist/datatable.js",
|
||||||
"module": "dist/datatable.js",
|
"module": "dist/datatable.js",
|
||||||
|
|||||||
@ -229,7 +229,11 @@ const createSelectionManager = (container, state) => {
|
|||||||
}
|
}
|
||||||
cells.push(current); return cells;
|
cells.push(current); return cells;
|
||||||
});
|
});
|
||||||
const { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
let { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
||||||
|
if (minRow === maxRow && minCol === maxCol) {
|
||||||
|
maxRow = Infinity;
|
||||||
|
maxCol = Infinity;
|
||||||
|
}
|
||||||
const body = container.querySelector('.dt-body');
|
const body = container.querySelector('.dt-body');
|
||||||
const rowNodes = body ? Array.from(body.childNodes).filter(n => n.classList?.contains('dt-body-row')) : [];
|
const rowNodes = body ? Array.from(body.childNodes).filter(n => n.classList?.contains('dt-body-row')) : [];
|
||||||
let anyRowChanged = false;
|
let anyRowChanged = false;
|
||||||
@ -427,22 +431,32 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
container.editCell = (row, field, cellNode) => {
|
container.editCell = (row, field, cellNode) => {
|
||||||
const overlay = container.querySelector('.dt-editor-overlay'), rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect();
|
const main = container.querySelector('.dt-main');
|
||||||
|
const overlay = container.querySelector('.dt-editor-overlay'), rect = cellNode.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
|
||||||
currentEditingNode = cellNode;
|
currentEditingNode = cellNode;
|
||||||
const formType = field.settings?.formType || field.type || 'text';
|
const formType = field.settings?.formType || field.type || 'text';
|
||||||
const form = overlay.querySelector('AutoForm');
|
const form = overlay.querySelector('AutoForm');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.data = row;
|
form.data = globalThis.NewState(globalThis.Util.clone(row));
|
||||||
form.state.schema = [{ ...field, type: formType, options: field.settings?.options || field.options, name: field.id, label: '' }];
|
form.state.schema = [{ ...field, type: formType, options: field.settings?.options || field.options, name: field.id, label: '' }];
|
||||||
}
|
}
|
||||||
const isComplex = ['textarea', 'TagsInput', 'checkbox', 'radio'].includes(formType);
|
const isComplex = ['textarea', 'TagsInput', 'checkbox', 'radio'].includes(formType);
|
||||||
|
let topPos = rect.top - mainRect.top + main.scrollTop - 1;
|
||||||
|
let leftPos = rect.left - mainRect.left + main.scrollLeft - 1;
|
||||||
|
let editorWidth = Math.max(rect.width + 2, isComplex ? 300 : 0);
|
||||||
|
|
||||||
|
const maxLeft = main.scrollWidth - editorWidth - 5;
|
||||||
|
if (leftPos > maxLeft) leftPos = Math.max(0, maxLeft);
|
||||||
|
|
||||||
Object.assign(overlay.style, {
|
Object.assign(overlay.style, {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
left: (rect.left - rootRect.left - 1) + 'px',
|
left: leftPos + 'px',
|
||||||
top: (rect.top - rootRect.top - 1) + 'px',
|
top: topPos + 'px',
|
||||||
width: Math.max(rect.width + 2, isComplex ? 300 : 0) + 'px',
|
width: editorWidth + 'px',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
minHeight: (rect.height + 2) + 'px',
|
minHeight: (rect.height + 2) + 'px',
|
||||||
|
maxHeight: Math.max(100, mainRect.height - (rect.top - mainRect.top) - 5) + 'px',
|
||||||
|
overflow: 'auto',
|
||||||
padding: '0'
|
padding: '0'
|
||||||
});
|
});
|
||||||
setTimeout(() => overlay.querySelector('input, textarea, select, .form-control')?.focus(), 30);
|
setTimeout(() => overlay.querySelector('input, textarea, select, .form-control')?.focus(), 30);
|
||||||
@ -456,19 +470,30 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
const input = _editorOverlay.querySelector('input:focus, select:focus, textarea:focus');
|
const input = _editorOverlay.querySelector('input:focus, select:focus, textarea:focus');
|
||||||
if (input) input.dispatchEvent(new Event(input.type === 'number' || input.tagName === 'SELECT' ? 'change' : 'input', { bubbles: true }));
|
if (input) input.dispatchEvent(new Event(input.type === 'number' || input.tagName === 'SELECT' ? 'change' : 'input', { bubbles: true }));
|
||||||
|
|
||||||
|
let hasChanges = false;
|
||||||
const schema = form.state.schema || [];
|
const schema = form.state.schema || [];
|
||||||
schema.forEach(field => {
|
schema.forEach(field => {
|
||||||
const row = currentEditingNode?.closest('.dt-row')?._ref?.item;
|
const row = currentEditingNode?.closest('.dt-row')?._ref?.item;
|
||||||
if (row) row[field.name] = form.data[field.name];
|
if (row && JSON.stringify(row[field.name]) !== JSON.stringify(form.data[field.name])) {
|
||||||
|
row[field.name] = form.data[field.name];
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (state.isBulkEdit) {
|
if (state.isBulkEdit) {
|
||||||
const { minRow, maxRow, fIdx } = state.isBulkEdit;
|
const { minRow, maxRow, fIdx } = state.isBulkEdit;
|
||||||
const field = state.fields[fIdx]; const newValue = form.data[field.id];
|
const field = state.fields[fIdx]; const newValue = form.data[field.id];
|
||||||
for (let i = minRow; i <= maxRow; i++) { if (state.list[i]) state.list[i][field.id] = newValue; }
|
for (let i = minRow; i <= maxRow; i++) {
|
||||||
|
if (state.list[i] && state.list[i][field.id] !== newValue) {
|
||||||
|
state.list[i][field.id] = newValue;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasChanges) {
|
||||||
|
state.list = [...state.list];
|
||||||
|
state.isDirty = true;
|
||||||
}
|
}
|
||||||
state.list = [...state.list];
|
|
||||||
state.isDirty = true;
|
|
||||||
}
|
}
|
||||||
_editorOverlay.style.display = 'none';
|
_editorOverlay.style.display = 'none';
|
||||||
if (form) { form.state.schema = []; form.data = null; }
|
if (form) { form.state.schema = []; form.data = null; }
|
||||||
@ -629,7 +654,7 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
|
|
||||||
state._MODE_ICONS = MODE_ICONS;
|
state._MODE_ICONS = MODE_ICONS;
|
||||||
}, globalThis.Util.makeDom(/*html*/`
|
}, globalThis.Util.makeDom(/*html*/`
|
||||||
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-hidden" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-visible" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
||||||
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
||||||
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
||||||
style="overflow-anchor:none; min-height: 0">
|
style="overflow-anchor:none; min-height: 0">
|
||||||
@ -657,6 +682,7 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
</template>
|
</template>
|
||||||
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
||||||
@ -666,17 +692,21 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div $if="this.state?.activeModes?.length" class="dt-filter-tabs d-flex overflow-auto border-bottom bg-light-subtle rounded-top py-1" style="white-space:nowrap; scrollbar-width: none;">
|
|
||||||
<template $each="this.state?.activeModes || []" as="m">
|
|
||||||
<div $class="px-2 py-1 cursor-pointer fs-5 \${this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === m ? 'text-primary border-bottom border-primary border-2' : 'text-muted'}" $title="m.toUpperCase()" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
|
||||||
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'}"></i>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template $if="this.state?.activeModes?.length">
|
<template $if="this.state?.activeModes?.length">
|
||||||
<div class="py-2 border-bottom" style="min-height: 48px">
|
<div class="py-2 border-bottom" style="min-height: 48px">
|
||||||
<input type="text" class="form-control form-control-sm mb-1" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<div class="input-group input-group-sm mb-1">
|
||||||
|
<input type="text" class="form-control" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
|
<button class="btn btn-outline-secondary border dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Search Mode">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[this.state?.filterConfig?.[this.state?.activeFieldId]?.mode] || 'bi-filter'}"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||||
|
<template $each="this.state?.activeModes || []" as="m">
|
||||||
|
<li><button class="dropdown-item d-flex align-items-center" type="button" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'} me-2"></i> <span $text="m.toUpperCase()"></span>
|
||||||
|
</button></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -696,16 +726,14 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 pt-2 border-top d-flex flex-column gap-1">
|
<div class="mt-3 pt-2 border-top d-flex gap-1 justify-content-between">
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.editField()"><i class="bi bi-pencil me-2"></i> Edit Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Edit Field" $onclick="this.editField()"><i class="bi bi-pencil"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.addField()"><i class="bi bi-plus-lg me-2"></i> Add Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Add Field" $onclick="this.addField()"><i class="bi bi-plus-lg"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-danger border d-flex align-items-center px-2 py-1" $onclick="this.deleteField()"><i class="bi bi-trash me-2"></i> Delete Field</button>
|
<button class="btn btn-sm btn-outline-danger flex-grow-1" title="Delete Field" $onclick="this.deleteField()"><i class="bi bi-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
|
||||||
|
|
||||||
<Modal $.id="this.id + '_field_modal'">
|
<Modal $.id="this.id + '_field_modal'">
|
||||||
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
@ -716,9 +744,9 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
|
|
||||||
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
||||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||||
<div class="btn-group shadow-sm">
|
<div class="d-flex align-items-center gap-1">
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
||||||
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
||||||
@ -727,8 +755,8 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button $if="this.state?.isDirty" class="btn btn-xs btn-primary px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save Changes</button>
|
<button $if="this.state?.isDirty" class="btn btn-sm btn-primary border-0 px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save</button>
|
||||||
<button $if="!this.state?.isDirty" class="btn btn-xs btn-light border px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Up to date</button>
|
<button $if="!this.state?.isDirty" class="btn btn-sm btn-light border-0 px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Saved</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -750,9 +778,11 @@ globalThis.Component.register('DataTable', container => {
|
|||||||
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
||||||
.menu-item-row .only-btn { opacity: 0; }
|
.menu-item-row .only-btn { opacity: 0; }
|
||||||
.menu-item-row:hover .only-btn { opacity: 1; }
|
.menu-item-row:hover .only-btn { opacity: 1; }
|
||||||
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; }
|
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; height: 100%; }
|
||||||
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
||||||
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0.375rem 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > .form-switch { padding-left: 2.5rem !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > textarea { min-height: 100px; resize: vertical; }
|
||||||
</style>
|
</style>
|
||||||
`))
|
`))
|
||||||
|
|
||||||
|
|||||||
@ -276,14 +276,14 @@
|
|||||||
};
|
};
|
||||||
const getSelectionBounds = () => {
|
const getSelectionBounds = () => {
|
||||||
if (!activeBounds) return null;
|
if (!activeBounds) return null;
|
||||||
let minRow = activeBounds.minRow, maxRow = activeBounds.maxRow, minCol = activeBounds.minCol, maxCol = activeBounds.maxCol;
|
let minRow2 = activeBounds.minRow, maxRow = activeBounds.maxRow, minCol2 = activeBounds.minCol, maxCol = activeBounds.maxCol;
|
||||||
multiSelections.forEach((s) => {
|
multiSelections.forEach((s) => {
|
||||||
minRow = Math.min(minRow, s.minRow);
|
minRow2 = Math.min(minRow2, s.minRow);
|
||||||
maxRow = Math.max(maxRow, s.maxRow);
|
maxRow = Math.max(maxRow, s.maxRow);
|
||||||
minCol = Math.min(minCol, s.minCol);
|
minCol2 = Math.min(minCol2, s.minCol);
|
||||||
maxCol = Math.max(maxCol, s.maxCol);
|
maxCol = Math.max(maxCol, s.maxCol);
|
||||||
});
|
});
|
||||||
return { minRow, maxRow, minCol, maxCol };
|
return { minRow: minRow2, maxRow, minCol: minCol2, maxCol };
|
||||||
};
|
};
|
||||||
const copy = async () => {
|
const copy = async () => {
|
||||||
const bounds = getSelectionBounds();
|
const bounds = getSelectionBounds();
|
||||||
@ -321,7 +321,11 @@
|
|||||||
cells.push(current);
|
cells.push(current);
|
||||||
return cells;
|
return cells;
|
||||||
});
|
});
|
||||||
const { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
let { minRow: startRow, minCol: startCol, maxRow, maxCol } = bounds;
|
||||||
|
if (minRow === maxRow && minCol === maxCol) {
|
||||||
|
maxRow = Infinity;
|
||||||
|
maxCol = Infinity;
|
||||||
|
}
|
||||||
const body = container.querySelector(".dt-body");
|
const body = container.querySelector(".dt-body");
|
||||||
const rowNodes = body ? Array.from(body.childNodes).filter((n) => {
|
const rowNodes = body ? Array.from(body.childNodes).filter((n) => {
|
||||||
var _a;
|
var _a;
|
||||||
@ -615,22 +619,30 @@
|
|||||||
});
|
});
|
||||||
container.editCell = (row, field, cellNode) => {
|
container.editCell = (row, field, cellNode) => {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const overlay = container.querySelector(".dt-editor-overlay"), rect = cellNode.getBoundingClientRect(), rootRect = container.getBoundingClientRect();
|
const main = container.querySelector(".dt-main");
|
||||||
|
const overlay = container.querySelector(".dt-editor-overlay"), rect = cellNode.getBoundingClientRect(), mainRect = main.getBoundingClientRect();
|
||||||
currentEditingNode = cellNode;
|
currentEditingNode = cellNode;
|
||||||
const formType = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text";
|
const formType = ((_a = field.settings) == null ? void 0 : _a.formType) || field.type || "text";
|
||||||
const form = overlay.querySelector("AutoForm");
|
const form = overlay.querySelector("AutoForm");
|
||||||
if (form) {
|
if (form) {
|
||||||
form.data = row;
|
form.data = globalThis.NewState(globalThis.Util.clone(row));
|
||||||
form.state.schema = [{ ...field, type: formType, options: ((_b = field.settings) == null ? void 0 : _b.options) || field.options, name: field.id, label: "" }];
|
form.state.schema = [{ ...field, type: formType, options: ((_b = field.settings) == null ? void 0 : _b.options) || field.options, name: field.id, label: "" }];
|
||||||
}
|
}
|
||||||
const isComplex = ["textarea", "TagsInput", "checkbox", "radio"].includes(formType);
|
const isComplex = ["textarea", "TagsInput", "checkbox", "radio"].includes(formType);
|
||||||
|
let topPos = rect.top - mainRect.top + main.scrollTop - 1;
|
||||||
|
let leftPos = rect.left - mainRect.left + main.scrollLeft - 1;
|
||||||
|
let editorWidth = Math.max(rect.width + 2, isComplex ? 300 : 0);
|
||||||
|
const maxLeft = main.scrollWidth - editorWidth - 5;
|
||||||
|
if (leftPos > maxLeft) leftPos = Math.max(0, maxLeft);
|
||||||
Object.assign(overlay.style, {
|
Object.assign(overlay.style, {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
left: rect.left - rootRect.left - 1 + "px",
|
left: leftPos + "px",
|
||||||
top: rect.top - rootRect.top - 1 + "px",
|
top: topPos + "px",
|
||||||
width: Math.max(rect.width + 2, isComplex ? 300 : 0) + "px",
|
width: editorWidth + "px",
|
||||||
height: "auto",
|
height: "auto",
|
||||||
minHeight: rect.height + 2 + "px",
|
minHeight: rect.height + 2 + "px",
|
||||||
|
maxHeight: Math.max(100, mainRect.height - (rect.top - mainRect.top) - 5) + "px",
|
||||||
|
overflow: "auto",
|
||||||
padding: "0"
|
padding: "0"
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -645,22 +657,31 @@
|
|||||||
if (save && form && form.data) {
|
if (save && form && form.data) {
|
||||||
const input = _editorOverlay.querySelector("input:focus, select:focus, textarea:focus");
|
const input = _editorOverlay.querySelector("input:focus, select:focus, textarea:focus");
|
||||||
if (input) input.dispatchEvent(new Event(input.type === "number" || input.tagName === "SELECT" ? "change" : "input", { bubbles: true }));
|
if (input) input.dispatchEvent(new Event(input.type === "number" || input.tagName === "SELECT" ? "change" : "input", { bubbles: true }));
|
||||||
|
let hasChanges = false;
|
||||||
const schema = form.state.schema || [];
|
const schema = form.state.schema || [];
|
||||||
schema.forEach((field) => {
|
schema.forEach((field) => {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const row = (_b = (_a = currentEditingNode == null ? void 0 : currentEditingNode.closest(".dt-row")) == null ? void 0 : _a._ref) == null ? void 0 : _b.item;
|
const row = (_b = (_a = currentEditingNode == null ? void 0 : currentEditingNode.closest(".dt-row")) == null ? void 0 : _a._ref) == null ? void 0 : _b.item;
|
||||||
if (row) row[field.name] = form.data[field.name];
|
if (row && JSON.stringify(row[field.name]) !== JSON.stringify(form.data[field.name])) {
|
||||||
|
row[field.name] = form.data[field.name];
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (state.isBulkEdit) {
|
if (state.isBulkEdit) {
|
||||||
const { minRow, maxRow, fIdx } = state.isBulkEdit;
|
const { minRow: minRow2, maxRow, fIdx } = state.isBulkEdit;
|
||||||
const field = state.fields[fIdx];
|
const field = state.fields[fIdx];
|
||||||
const newValue = form.data[field.id];
|
const newValue = form.data[field.id];
|
||||||
for (let i = minRow; i <= maxRow; i++) {
|
for (let i = minRow2; i <= maxRow; i++) {
|
||||||
if (state.list[i]) state.list[i][field.id] = newValue;
|
if (state.list[i] && state.list[i][field.id] !== newValue) {
|
||||||
|
state.list[i][field.id] = newValue;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.list = [...state.list];
|
if (hasChanges) {
|
||||||
state.isDirty = true;
|
state.list = [...state.list];
|
||||||
|
state.isDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_editorOverlay.style.display = "none";
|
_editorOverlay.style.display = "none";
|
||||||
if (form) {
|
if (form) {
|
||||||
@ -851,7 +872,7 @@
|
|||||||
}, globalThis.Util.makeDom(
|
}, globalThis.Util.makeDom(
|
||||||
/*html*/
|
/*html*/
|
||||||
`
|
`
|
||||||
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-hidden" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
<div class="dt-root d-flex flex-column h-100 border bg-body text-body overflow-visible" style="position:relative; user-select:none; outline: none; min-height: 0" tabindex="0">
|
||||||
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
<div class="dt-main flex-grow-1 overflow-auto" $onscroll="this.onScroll()"
|
||||||
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
$onmousedown="this.onMainMouseDown(event)" $onmouseover="this.onMainMouseOver(event)" $ondblclick="this.onMainDblClick(event)"
|
||||||
style="overflow-anchor:none; min-height: 0">
|
style="overflow-anchor:none; min-height: 0">
|
||||||
@ -879,6 +900,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
<div class="dt-spacer-post flex-shrink-0" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
<div class="dt-column-menu bg-body shadow-lg rounded p-2" style="display:none; position:absolute; z-index:2000; min-width:240px; max-width:300px; border: 1px solid var(--bs-primary)">
|
||||||
@ -888,17 +910,21 @@
|
|||||||
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
<button $class="btn btn-xs flex-grow-1 d-flex align-items-center justify-content-center \${this.state?.sortConfig?.direction === 'desc' && this.state?.sortConfig?.fieldId === this.state?.activeFieldId ? 'btn-primary' : 'btn-outline-secondary border'}" $onclick="this.setSort('desc')"><i class="bi bi-sort-alpha-up-alt me-1"></i> DESC</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div $if="this.state?.activeModes?.length" class="dt-filter-tabs d-flex overflow-auto border-bottom bg-light-subtle rounded-top py-1" style="white-space:nowrap; scrollbar-width: none;">
|
|
||||||
<template $each="this.state?.activeModes || []" as="m">
|
|
||||||
<div $class="px-2 py-1 cursor-pointer fs-5 \${this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === m ? 'text-primary border-bottom border-primary border-2' : 'text-muted'}" $title="m.toUpperCase()" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
|
||||||
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'}"></i>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template $if="this.state?.activeModes?.length">
|
<template $if="this.state?.activeModes?.length">
|
||||||
<div class="py-2 border-bottom" style="min-height: 48px">
|
<div class="py-2 border-bottom" style="min-height: 48px">
|
||||||
<input type="text" class="form-control form-control-sm mb-1" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<div class="input-group input-group-sm mb-1">
|
||||||
|
<input type="text" class="form-control" $placeholder="(this.state?.filterConfig?.[this.state?.activeFieldId]?.mode || 'Search').toUpperCase() + '...'" $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
|
<button class="btn btn-outline-secondary border dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Search Mode">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[this.state?.filterConfig?.[this.state?.activeFieldId]?.mode] || 'bi-filter'}"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||||
|
<template $each="this.state?.activeModes || []" as="m">
|
||||||
|
<li><button class="dropdown-item d-flex align-items-center" type="button" $onclick="this.state.filterConfig[this.state.activeFieldId].mode = m; this.state.filterConfig = {...this.state.filterConfig}">
|
||||||
|
<i $class="bi \${this.state?._MODE_ICONS?.[m] || 'bi-filter'} me-2"></i> <span $text="m.toUpperCase()"></span>
|
||||||
|
</button></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
<input $if="this.state?.filterConfig?.[this.state?.activeFieldId]?.mode === 'between'" type="text" class="form-control form-control-sm" placeholder="And..." $bind="this.state?.filterConfig?.[this.state?.activeFieldId].value2" $onkeydown="if(event.key==='Enter'){this.applySortFilter();this.hideColumnMenu();}">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -918,16 +944,14 @@
|
|||||||
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
<span class="cursor-pointer text-primary small fw-bold" $onclick="this.clearColumnSettings()"><i class="bi bi-x-circle me-1"></i> Clear Filter</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 pt-2 border-top d-flex flex-column gap-1">
|
<div class="mt-3 pt-2 border-top d-flex gap-1 justify-content-between">
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.editField()"><i class="bi bi-pencil me-2"></i> Edit Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Edit Field" $onclick="this.editField()"><i class="bi bi-pencil"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-secondary border d-flex align-items-center px-2 py-1" $onclick="this.addField()"><i class="bi bi-plus-lg me-2"></i> Add Field</button>
|
<button class="btn btn-sm btn-outline-secondary flex-grow-1" title="Add Field" $onclick="this.addField()"><i class="bi bi-plus-lg"></i></button>
|
||||||
<button class="btn btn-xs btn-outline-danger border d-flex align-items-center px-2 py-1" $onclick="this.deleteField()"><i class="bi bi-trash me-2"></i> Delete Field</button>
|
<button class="btn btn-sm btn-outline-danger flex-grow-1" title="Delete Field" $onclick="this.deleteField()"><i class="bi bi-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dt-editor-overlay dt-editor-container" style="display: none; position: absolute; z-index: 1000; background: var(--bs-body-bg); box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: 1px solid var(--bs-primary); padding: 0;"><AutoForm nobutton inline class="h-100 w-100" $onsubmit="event.preventDefault(); thisNode.closest('DataTable').hideEditor(true)"></AutoForm></div>
|
|
||||||
|
|
||||||
<Modal $.id="this.id + '_field_modal'">
|
<Modal $.id="this.id + '_field_modal'">
|
||||||
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
<div slot="body"><AutoForm nobutton class="p-3"></AutoForm></div>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
@ -938,9 +962,9 @@
|
|||||||
|
|
||||||
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
<div class="dt-footer border-top bg-light d-flex align-items-center px-3 py-1 shadow-sm" style="height:40px; z-index: 10">
|
||||||
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
<div class="d-flex align-items-center gap-3 flex-grow-1">
|
||||||
<div class="btn-group shadow-sm">
|
<div class="d-flex align-items-center gap-1">
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.addRow()" title="Add Row"><i class="bi bi-plus-lg text-primary me-1"></i> Add</button>
|
||||||
<button class="btn btn-xs btn-white border d-flex align-items-center px-2" style="background:white" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
<button class="btn btn-sm btn-light border-0 d-flex align-items-center px-2 py-1" $onclick="this.deleteSelectedRow()" $disabled="!this.state?.selectedRowCount" title="Delete Selected Rows"><i class="bi bi-trash text-danger me-1"></i> Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
<div class="vr h-50 my-auto text-muted opacity-25"></div>
|
||||||
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
<div class="d-flex align-items-center gap-2 text-muted" style="font-size: 0.75rem">
|
||||||
@ -949,8 +973,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button $if="this.state?.isDirty" class="btn btn-xs btn-primary px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save Changes</button>
|
<button $if="this.state?.isDirty" class="btn btn-sm btn-primary border-0 px-3 shadow-sm d-flex align-items-center fw-bold" $onclick="this.saveChanges()"><i class="bi bi-cloud-upload me-1"></i> Save</button>
|
||||||
<button $if="!this.state?.isDirty" class="btn btn-xs btn-light border px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Up to date</button>
|
<button $if="!this.state?.isDirty" class="btn btn-sm btn-light border-0 px-3 text-muted disabled d-flex align-items-center" disabled><i class="bi bi-cloud-check me-1"></i> Saved</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -975,9 +999,11 @@
|
|||||||
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
.dt-filter-tabs div:hover i { color: var(--bs-primary); }
|
||||||
.menu-item-row .only-btn { opacity: 0; }
|
.menu-item-row .only-btn { opacity: 0; }
|
||||||
.menu-item-row:hover .only-btn { opacity: 1; }
|
.menu-item-row:hover .only-btn { opacity: 1; }
|
||||||
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; }
|
.dt-editor-overlay .auto-form-root form { gap: 0 !important; margin: 0 !important; height: 100%; }
|
||||||
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
.dt-editor-overlay [control-wrapper] { width: 100%; margin: 0 !important; min-height: 100% !important; align-items: stretch !important; }
|
||||||
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
.dt-editor-overlay [control-wrapper] > .d-flex { padding: 0.375rem 0.5rem; justify-content: flex-start !important; align-items: center !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > .form-switch { padding-left: 2.5rem !important; }
|
||||||
|
.dt-editor-overlay [control-wrapper] > textarea { min-height: 100px; resize: vertical; }
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
));
|
));
|
||||||
|
|||||||
2
test/lib/datatable.min.js
vendored
2
test/lib/datatable.min.js
vendored
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const Util = {
|
const Util = {
|
||||||
clone: globalThis.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
|
clone: globalThis.structuredClone ? (obj) => globalThis.structuredClone(obj) : (obj) => JSON.parse(JSON.stringify(obj)),
|
||||||
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
||||||
unbase64: (str) => new TextDecoder().decode(Uint8Array.from(atob(str), (c) => c.charCodeAt(0))),
|
unbase64: (str) => new TextDecoder().decode(Uint8Array.from(atob(str), (c) => c.charCodeAt(0))),
|
||||||
urlbase64: (str) => Util.base64(str).replace(/[+/=]/g, (m) => ({ "+": "-", "/": "", "=": "" })[m]),
|
urlbase64: (str) => Util.base64(str).replace(/[+/=]/g, (m) => ({ "+": "-", "/": "", "=": "" })[m]),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user