release: v1.0.2
This commit is contained in:
parent
58081348ab
commit
36f39bfa08
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,5 +1,20 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.2 (2026-05-17)
|
||||||
|
|
||||||
|
### 新特性
|
||||||
|
- **AutoForm**: 新增 `inline` 模式,支持紧凑的单行表单布局,并增强了与 `DataTable` 的联动能力(数据变化自动刷新)。
|
||||||
|
- **Resizer**:
|
||||||
|
- 支持 `$bind` 指令,实现尺寸的双向绑定。
|
||||||
|
- 新增 `resizing` 和 `change` 事件,便于监听实时与最终缩放结果。
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
- **UI**:
|
||||||
|
- `Modal`, `Dialog`, `Toast` 默认主题色由 `body` 改为 `primary`,提升视觉引导。
|
||||||
|
- `Toast` 增加 `rounded` 圆角样式。
|
||||||
|
- 统一所有表单控件(input/select/textarea)在 `AutoForm` 中的设置默认为 `{}`。
|
||||||
|
- **FastList**: 重构虚拟滚动逻辑为独立的 `VirtualScroll` 模块,提升代码复用性与可维护性。
|
||||||
|
|
||||||
## v1.0.1 (2026-05-14)
|
## v1.0.1 (2026-05-14)
|
||||||
- **Perf**: 优化 `FastList` 渲染逻辑,消除 Layout Thrashing,提升复杂列表(GroupedList/Tree)滚动性能达 10 倍以上。
|
- **Perf**: 优化 `FastList` 渲染逻辑,消除 Layout Thrashing,提升复杂列表(GroupedList/Tree)滚动性能达 10 倍以上。
|
||||||
- **Refactor**: 采用 `requestAnimationFrame` 优化初始化高度计算,减少闪烁。
|
- **Refactor**: 采用 `requestAnimationFrame` 优化初始化高度计算,减少闪烁。
|
||||||
|
|||||||
231
dist/base.js
vendored
231
dist/base.js
vendored
@ -107,7 +107,7 @@ Component.register("Modal", (container) => {
|
|||||||
`
|
`
|
||||||
<div class="modal fade" data-bs-backdrop="static">
|
<div class="modal fade" data-bs-backdrop="static">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div $class="modal-content text-bg-\${this.state?.type || 'body'}">
|
<div $class="modal-content text-bg-\${this.state?.type || 'primary'}">
|
||||||
<div slot-id="header" class="modal-header">
|
<div slot-id="header" class="modal-header">
|
||||||
<h6 class="modal-title" $text="this.state?.title"></h6>
|
<h6 class="modal-title" $text="this.state?.title"></h6>
|
||||||
<button type="button" class="btn btn-link ms-2 bi bi-x-lg link-reset" style="color:inherit" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn btn-link ms-2 bi bi-x-lg link-reset" style="color:inherit" data-bs-dismiss="modal"></button>
|
||||||
@ -124,7 +124,7 @@ Component.register("Dialog", Component.getSetupFunction("Modal"), Util.makeDom(
|
|||||||
`
|
`
|
||||||
<div class="modal fade" data-bs-backdrop="static">
|
<div class="modal fade" data-bs-backdrop="static">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div $class="modal-content text-bg-\${this.state?.type || 'body'}">
|
<div $class="modal-content text-bg-\${this.state?.type || 'primary'}">
|
||||||
<div $if="this.state?.title" class="modal-header" $text="this.state?.title"></div>
|
<div $if="this.state?.title" class="modal-header" $text="this.state?.title"></div>
|
||||||
<div slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
<div slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -181,7 +181,7 @@ Component.register("Toast", (container) => {
|
|||||||
/*html*/
|
/*html*/
|
||||||
`
|
`
|
||||||
<div class="toast align-items-center border-0 m-1">
|
<div class="toast align-items-center border-0 m-1">
|
||||||
<div $class="toast-body p-3 text-bg-\${this.state?.type}">
|
<div $class="toast-body rounded p-3 text-bg-\${this.state?.type}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<span style="white-space:pre-wrap" class="fs-6" $text="this.state?.message"></span>
|
<span style="white-space:pre-wrap" class="fs-6" $text="this.state?.message"></span>
|
||||||
@ -202,7 +202,7 @@ Component.register("Toast", (container) => {
|
|||||||
UI$1.toast = function(message, options = {}) {
|
UI$1.toast = function(message, options = {}) {
|
||||||
const delay = options.delay ?? 5e3;
|
const delay = options.delay ?? 5e3;
|
||||||
const t = document.createElement("Toast");
|
const t = document.createElement("Toast");
|
||||||
t.state = { delay, left: delay ? delay / 1e3 : void 0, type: options.type || "body", message, buttons: options.buttons || [] };
|
t.state = { delay, left: delay ? delay / 1e3 : void 0, type: options.type || "primary", message, buttons: options.buttons || [] };
|
||||||
$(`[toast-container="${options.container || "default"}"]`).appendChild(t);
|
$(`[toast-container="${options.container || "default"}"]`).appendChild(t);
|
||||||
Promise.resolve().then(() => t.show());
|
Promise.resolve().then(() => t.show());
|
||||||
};
|
};
|
||||||
@ -212,10 +212,19 @@ UI$1.toastConfirm = function(message, options = {}) {
|
|||||||
Component.register("AutoForm", (container) => {
|
Component.register("AutoForm", (container) => {
|
||||||
if (!container.state.schema) container.state.schema = [];
|
if (!container.state.schema) container.state.schema = [];
|
||||||
container.vertical = container.hasAttribute("vertical");
|
container.vertical = container.hasAttribute("vertical");
|
||||||
|
container.inline = container.hasAttribute("inline");
|
||||||
container.request = { method: "POST" };
|
container.request = { method: "POST" };
|
||||||
container.response = {};
|
container.response = {};
|
||||||
container.result = null;
|
container.result = null;
|
||||||
|
if (!container.data || !container.data.__watch) {
|
||||||
container.data = NewState(container.data || {});
|
container.data = NewState(container.data || {});
|
||||||
|
}
|
||||||
|
container.data.__watch("*", () => {
|
||||||
|
if (container.inline) {
|
||||||
|
const dt = container.closest("DataTable");
|
||||||
|
if (dt && dt.refresh) dt.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
container.form = $(container, "form");
|
container.form = $(container, "form");
|
||||||
container.submit = (opt = {}) => {
|
container.submit = (opt = {}) => {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
@ -240,27 +249,29 @@ Component.register("AutoForm", (container) => {
|
|||||||
}, Util.makeDom(
|
}, Util.makeDom(
|
||||||
/*html*/
|
/*html*/
|
||||||
`
|
`
|
||||||
<div>
|
<div $class="'auto-form-root' + (this.inline ? ' auto-form-inline min-h-100 w-100' : '')">
|
||||||
<form $class="align-items-center \${this.vertical?'':'auto-grid-form'}" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
<form $class="'h-100 w-100 d-flex ' + (this.inline ? 'align-items-center' : (this.vertical ? 'flex-column' : 'auto-grid-form'))" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
||||||
<div $each="this.state.schema || []" style="display:contents">
|
<div $each="this.state.schema || []" style="display:contents">
|
||||||
<label $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
<label $if="!this.inline" $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||||
<div control-wrapper class="mb-3">
|
<div control-wrapper $class="this.inline ? 'flex-grow-1 h-100 d-flex align-items-center' : 'mb-3'">
|
||||||
<input $if="['text', 'password', 'email', 'number', 'date', 'datetime', 'file'].includes(item.type)" $name="item.name" class="form-control" $type="item.type" $.="item.setting" $bind="this.data[item.name]">
|
<input $if="['text', 'password', 'email', 'number', 'date', 'datetime', 'file'].includes(item.type)" $name="item.name" class="form-control" $type="item.type" $.="item.setting || {}" $bind="this.data[item.name]" $class="item.type === 'number' ? 'text-end' : ''">
|
||||||
<select $if="item.type === 'select'" $name="item.name" class="form-select" $.="item.setting" $bind="this.data[item.name]">
|
<select $if="item.type === 'select'" $name="item.name" class="form-select" $.="item.setting || {}" $bind="this.data[item.name]">
|
||||||
<option value="" $if="item.placeholder" $text="item.placeholder" disabled selected></option>
|
<option value="" $if="item.placeholder" $text="item.placeholder" disabled></option>
|
||||||
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
||||||
</select>
|
</select>
|
||||||
<div $if="['checkbox', 'radio'].includes(item.type)" >
|
<div $if="['checkbox', 'radio'].includes(item.type)" $class="this.inline ? 'h-100 d-flex align-items-center px-2 gap-3 justify-content-center w-100' : ''" style="white-space:nowrap">
|
||||||
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="form-check\${item.vertical ? '' : ' form-check-inline'}">
|
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="'form-check' + (item.vertical ? '' : ' form-check-inline') + ' mb-0 d-flex align-items-center p-0'">
|
||||||
<input $name="item.name" class="form-check-input" $type="item.type" $.="item.setting" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
<input $name="item.name" class="form-check-input me-1" $type="item.type" $.="item.setting || {}" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
||||||
<span $text="option" class="form-check-label"></span>
|
<span $if="!this.inline || (item.options && item.options.length > 0)" $text="option" class="form-check-label"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div $if="item.type==='switch'" class="form-check form-switch fs-4"><input $name="item.name" class="form-check-input my-0" type="checkbox" $bind="this.data[item.name]"></div>
|
<div $if="item.type==='switch'" class="form-check form-switch fs-4 h-100 d-flex align-items-center px-2 m-0 justify-content-center w-100" style="padding-left:0">
|
||||||
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting" $bind="this.data[item.name]"></textarea>
|
<input $name="item.name" class="form-check-input m-0" type="checkbox" style="cursor:pointer" $bind="this.data[item.name]">
|
||||||
|
</div>
|
||||||
|
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting || {}" $bind="this.data[item.name]"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end align-items-baseline gap-3 mt-3" style="grid-column:1/-1">
|
<div $if="!this.inline" class="d-flex justify-content-end align-items-baseline gap-3 mt-3" style="grid-column:1/-1">
|
||||||
<div slot-id="actions"></div>
|
<div slot-id="actions"></div>
|
||||||
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
||||||
</div>
|
</div>
|
||||||
@ -269,7 +280,14 @@ Component.register("AutoForm", (container) => {
|
|||||||
`
|
`
|
||||||
), Util.makeDom(
|
), Util.makeDom(
|
||||||
/*html*/
|
/*html*/
|
||||||
`<style>@media (min-width: 576px) { .auto-grid-form {display: grid;grid-template-columns: max-content 1fr} .auto-grid-form .col-form-label {text-align: right; margin-bottom: 1rem;padding-right: 1rem;max-width: 200px} }</style>`
|
`<style>
|
||||||
|
@media (min-width: 576px) { .auto-grid-form {display: grid;grid-template-columns: max-content 1fr} .auto-grid-form .col-form-label {text-align: right; margin-bottom: 1rem;padding-right: 1rem;max-width: 200px} }
|
||||||
|
.auto-form-inline { background-color: var(--bs-body-bg); }
|
||||||
|
.auto-form-inline.min-h-100 { min-height: 100%; height: auto; }
|
||||||
|
.auto-form-inline .form-control, .auto-form-inline .form-select { border-radius: 0; border: none; background-color: transparent !important; min-height: 100%; height: auto; width: 100%; padding: 0 8px; box-shadow: none; color: inherit; }
|
||||||
|
.auto-form-inline .form-select { background-position: right 4px center; background-size: 12px 10px; padding-right: 20px; }
|
||||||
|
.auto-form-inline textarea.form-control { padding: 8px; min-height: 100px; height: auto; }
|
||||||
|
</style>`
|
||||||
));
|
));
|
||||||
const _pendingAutoFormComponents = [];
|
const _pendingAutoFormComponents = [];
|
||||||
const AutoForm = {
|
const AutoForm = {
|
||||||
@ -283,9 +301,7 @@ const AutoForm = {
|
|||||||
var _a;
|
var _a;
|
||||||
const template = Component.getTemplate("AutoForm");
|
const template = Component.getTemplate("AutoForm");
|
||||||
if (template) {
|
if (template) {
|
||||||
(_a = $(template.content, "[control-wrapper]")) == null ? void 0 : _a.appendChild(Util.makeDom(`<${name} $if="item.type.toUpperCase() === '${name.toUpperCase()}'" $name="item.name" $.="item.setting" $bind="this.data[item.name]"></${name}>`));
|
(_a = $(template.content, "[control-wrapper]")) == null ? void 0 : _a.appendChild(Util.makeDom(`<${name} $if="item.type.toUpperCase() === '${name.toUpperCase()}'" $name="item.name" $.="item.setting || {}" $bind="this.data[item.name]"></${name}>`));
|
||||||
} else if (document.readyState !== "loading") {
|
|
||||||
console.error("AutoForm template not found during registration of", name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -337,30 +353,62 @@ Component.register("TagsInput", (container) => {
|
|||||||
`<style>TagsInput button:focus {background-color:var(--bs-btn-hover-bg);color:var(--bs-btn-hover-color)}</style>`
|
`<style>TagsInput button:focus {background-color:var(--bs-btn-hover-bg);color:var(--bs-btn-hover-color)}</style>`
|
||||||
));
|
));
|
||||||
AutoForm.register("TagsInput");
|
AutoForm.register("TagsInput");
|
||||||
const FastListComponent = Component.register("FastList", (container) => {
|
const VirtualScroll = () => {
|
||||||
const itemHeights = /* @__PURE__ */ new Map();
|
const itemHeights = /* @__PURE__ */ new Map();
|
||||||
const groupHeights = /* @__PURE__ */ new Map();
|
const groupHeights = /* @__PURE__ */ new Map();
|
||||||
let groupItemCount = 1;
|
let groupItemCount = 1;
|
||||||
container.state.renderedList = [];
|
|
||||||
const avg = Util.newAvg();
|
const avg = Util.newAvg();
|
||||||
let containerPaddingTop = 0;
|
let padTop = 0;
|
||||||
let containerRowGap = 0;
|
let rowGap = 0;
|
||||||
let topMargin = 0;
|
let topMargin = 0;
|
||||||
let itemMarginTop = null;
|
let itemMarginTop = null;
|
||||||
let itemMarginBottom = null;
|
let itemMarginBottom = null;
|
||||||
let listStartIndex = 0;
|
|
||||||
let visibleStartIndex = 0;
|
|
||||||
let visibleCount = 10;
|
|
||||||
let listInited = false;
|
let listInited = false;
|
||||||
container.onItemUpdate = (index2, node) => {
|
return {
|
||||||
const absoluteIndex = index2 + listStartIndex;
|
// 核心1:重置与首屏预渲染
|
||||||
|
reset: (list, container) => {
|
||||||
|
listInited = false;
|
||||||
|
itemHeights.clear();
|
||||||
|
groupHeights.clear();
|
||||||
|
avg.clear();
|
||||||
|
topMargin = 0;
|
||||||
|
itemMarginTop = null;
|
||||||
|
itemMarginBottom = null;
|
||||||
|
if (!(list == null ? void 0 : list.length)) return [];
|
||||||
|
const size = list.length;
|
||||||
|
groupItemCount = Math.ceil(Math.sqrt(size)) || 10;
|
||||||
|
const style = window.getComputedStyle(container);
|
||||||
|
padTop = parseFloat(style.paddingTop) || 0;
|
||||||
|
rowGap = parseFloat(style.rowGap) || 0;
|
||||||
|
return list.slice(0, Math.min(30, size));
|
||||||
|
},
|
||||||
|
// 核心2:无痛初始化缓存 (通过 rAF 调用)
|
||||||
|
init: (list, refreshCallback) => {
|
||||||
|
if (listInited) return;
|
||||||
|
const size = list.length;
|
||||||
|
const defaultHeight = avg.get() || 32;
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < size; i += groupItemCount) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let j = i; j < Math.min(i + groupItemCount, size); j++) {
|
||||||
|
sum += itemHeights.get(j);
|
||||||
|
}
|
||||||
|
groupHeights.set(i, sum);
|
||||||
|
}
|
||||||
|
listInited = true;
|
||||||
|
refreshCallback();
|
||||||
|
},
|
||||||
|
// 核心3:原汁原味的极速高度更新
|
||||||
|
update: (absoluteIndex, node) => {
|
||||||
if (itemMarginTop === null) {
|
if (itemMarginTop === null) {
|
||||||
const style = window.getComputedStyle(node);
|
const style = window.getComputedStyle(node);
|
||||||
itemMarginTop = parseFloat(style.marginTop) || 0;
|
itemMarginTop = parseFloat(style.marginTop) || 0;
|
||||||
itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
||||||
}
|
}
|
||||||
if (absoluteIndex === 0) topMargin = itemMarginTop;
|
if (absoluteIndex === 0) topMargin = itemMarginTop;
|
||||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap;
|
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap;
|
||||||
const oldHeight = itemHeights.get(absoluteIndex);
|
const oldHeight = itemHeights.get(absoluteIndex);
|
||||||
if (newHeight !== oldHeight) {
|
if (newHeight !== oldHeight) {
|
||||||
itemHeights.set(absoluteIndex, newHeight);
|
itemHeights.set(absoluteIndex, newHeight);
|
||||||
@ -369,38 +417,37 @@ const FastListComponent = Component.register("FastList", (container) => {
|
|||||||
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
|
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
|
||||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
|
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
container.refresh = () => {
|
// 核心4:原汁原味的状态机滚动计算
|
||||||
if (!listInited) return;
|
calc: (container, list) => {
|
||||||
visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32));
|
if (!listInited || !list) return null;
|
||||||
let list = [];
|
const size = list.length;
|
||||||
const fullList = container.state.list;
|
const visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32));
|
||||||
const fullSize = fullList.length;
|
let prev = padTop + topMargin + rowGap;
|
||||||
let i = 0;
|
|
||||||
let prev = containerPaddingTop + topMargin + containerRowGap;
|
|
||||||
let post = 0;
|
let post = 0;
|
||||||
let starus = 0;
|
let status = 0;
|
||||||
for (i = 0; i < fullSize; i++) {
|
let listStartIndex = 0;
|
||||||
if (starus === 0) {
|
let listEndIndex = 0;
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
if (status === 0) {
|
||||||
const gh = groupHeights.get(i);
|
const gh = groupHeights.get(i);
|
||||||
if (gh && prev + gh < container.scrollTop) {
|
if (gh && prev + gh < container.scrollTop) {
|
||||||
prev += gh;
|
prev += gh;
|
||||||
i += Math.min(groupItemCount, fullSize - i) - 1;
|
i += Math.min(groupItemCount, size - i) - 1;
|
||||||
} else {
|
} else {
|
||||||
const ih = itemHeights.get(i);
|
const ih = itemHeights.get(i);
|
||||||
if (prev + ih < container.scrollTop) {
|
if (prev + ih < container.scrollTop) {
|
||||||
prev += ih;
|
prev += ih;
|
||||||
} else {
|
} else {
|
||||||
starus = 1;
|
status = 1;
|
||||||
visibleStartIndex = Math.max(0, i);
|
let visibleStartIndex = Math.max(0, i);
|
||||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
|
listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
|
||||||
const listEndIndex = Math.min(listStartIndex + visibleCount * 3, fullSize);
|
listEndIndex = Math.min(listStartIndex + visibleCount * 3, size);
|
||||||
i = listEndIndex - 1;
|
i = listEndIndex - 1;
|
||||||
list = fullList.slice(listStartIndex, listEndIndex);
|
|
||||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
|
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (starus === 1) {
|
} else if (status === 1) {
|
||||||
const gh = groupHeights.get(i);
|
const gh = groupHeights.get(i);
|
||||||
if (gh) {
|
if (gh) {
|
||||||
post += gh;
|
post += gh;
|
||||||
@ -410,40 +457,36 @@ const FastListComponent = Component.register("FastList", (container) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container.state.prevHeight = prev - containerPaddingTop - topMargin - containerRowGap;
|
return {
|
||||||
container.state.postHeight = post;
|
prevHeight: prev - padTop - topMargin - rowGap,
|
||||||
container.state.renderedList = list;
|
postHeight: post,
|
||||||
|
renderedList: list.slice(listStartIndex, listEndIndex),
|
||||||
|
listStartIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const FastListComponent = Component.register("FastList", (container) => {
|
||||||
|
const vs = VirtualScroll();
|
||||||
|
container.state.renderedList = [];
|
||||||
|
container.onItemUpdate = (index2, node) => {
|
||||||
|
vs.update(index2 + (container.state._listStartIndex || 0), node);
|
||||||
|
};
|
||||||
|
container.refresh = () => {
|
||||||
|
const res = vs.calc(container, container.state.list);
|
||||||
|
if (res) {
|
||||||
|
container.state.prevHeight = res.prevHeight;
|
||||||
|
container.state.postHeight = res.postHeight;
|
||||||
|
container.state._listStartIndex = res.listStartIndex;
|
||||||
|
container.state.renderedList = res.renderedList;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
container.state.__watch("list", (list) => {
|
container.state.__watch("list", (list) => {
|
||||||
listInited = false;
|
container.state._listStartIndex = 0;
|
||||||
itemHeights.clear();
|
container.state.renderedList = vs.reset(list, container) || [];
|
||||||
groupHeights.clear();
|
|
||||||
avg.clear();
|
|
||||||
topMargin = 0;
|
|
||||||
itemMarginTop = null;
|
|
||||||
itemMarginBottom = null;
|
|
||||||
if (!(list == null ? void 0 : list.length)) return;
|
|
||||||
const listSize = list.length || 0;
|
|
||||||
groupItemCount = Math.ceil(Math.sqrt(listSize)) || 10;
|
|
||||||
const style = window.getComputedStyle(container);
|
|
||||||
containerPaddingTop = parseFloat(style.paddingTop) || 0;
|
|
||||||
containerRowGap = parseFloat(style.rowGap) || 0;
|
|
||||||
container.state.renderedList = list.slice(0, Math.min(30, listSize)) || [];
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (listInited || container.state.list !== list) return;
|
if (container.state.list !== list) return;
|
||||||
const defaultHeight = avg.get() || 32;
|
vs.init(list, container.refresh);
|
||||||
for (let i = 0; i < listSize; i++) {
|
|
||||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
|
||||||
sum += itemHeights.get(j);
|
|
||||||
}
|
|
||||||
groupHeights.set(i, sum);
|
|
||||||
}
|
|
||||||
listInited = true;
|
|
||||||
container.refresh();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, Util.makeDom(
|
}, Util.makeDom(
|
||||||
@ -707,19 +750,32 @@ if (typeof document !== "undefined") {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Component.register("Resizer", (container) => {
|
Component.register("Resizer", (container) => {
|
||||||
const isVertical = container.hasAttribute("vertical");
|
container.isVertical = container.hasAttribute("vertical");
|
||||||
const min = parseInt(container.getAttribute("min")) || 10;
|
const min = parseInt(container.getAttribute("min")) || 10;
|
||||||
const max = parseInt(container.getAttribute("max")) || 1e3;
|
const max = parseInt(container.getAttribute("max")) || 1e3;
|
||||||
const target = container.target || container.previousElementSibling;
|
const target = container.target || container.previousElementSibling;
|
||||||
|
container.addEventListener("bind", (e) => {
|
||||||
|
if (e.detail !== void 0 && e.detail !== null) {
|
||||||
|
target.style[container.isVertical ? "height" : "width"] = e.detail + "px";
|
||||||
|
}
|
||||||
|
});
|
||||||
const getSize = (startSize, w, h) => {
|
const getSize = (startSize, w, h) => {
|
||||||
const newSize = startSize + (isVertical ? h : w);
|
const newSize = startSize + (container.isVertical ? h : w);
|
||||||
return newSize < min ? min : newSize > max ? max : newSize;
|
return newSize < min ? min : newSize > max ? max : newSize;
|
||||||
};
|
};
|
||||||
container.addEventListener("mousedown", (event) => {
|
container.addEventListener("mousedown", (event) => {
|
||||||
const startSize = isVertical ? target.offsetHeight : target.offsetWidth;
|
const startSize = container.isVertical ? target.offsetHeight : target.offsetWidth;
|
||||||
MouseMover.start(event, {
|
MouseMover.start(event, {
|
||||||
onmousemove: ({ w, h }) => target.style[isVertical ? "height" : "width"] = getSize(startSize, w, h) + "px",
|
onmousemove: ({ w, h }) => {
|
||||||
onmouseup: ({ w, h }) => container.dispatchEvent(new CustomEvent("resize", { detail: { oldSize: startSize, newSize: getSize(startSize, w, h) }, bubbles: false }))
|
const newSize = getSize(startSize, w, h);
|
||||||
|
target.style[container.isVertical ? "height" : "width"] = newSize + "px";
|
||||||
|
container.dispatchEvent(new CustomEvent("resizing", { detail: { oldSize: startSize, newSize }, bubbles: false }));
|
||||||
|
},
|
||||||
|
onmouseup: ({ w, h }) => {
|
||||||
|
const newSize = getSize(startSize, w, h);
|
||||||
|
container.dispatchEvent(new CustomEvent("resize", { detail: { oldSize: startSize, newSize }, bubbles: false }));
|
||||||
|
container.dispatchEvent(new CustomEvent("change", { detail: newSize, bubbles: false }));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, Util.makeDom(
|
}, Util.makeDom(
|
||||||
@ -755,5 +811,6 @@ export {
|
|||||||
HTTP,
|
HTTP,
|
||||||
MouseMover,
|
MouseMover,
|
||||||
State,
|
State,
|
||||||
UI$1 as UI
|
UI$1 as UI,
|
||||||
|
VirtualScroll
|
||||||
};
|
};
|
||||||
|
|||||||
2
dist/base.min.js
vendored
2
dist/base.min.js
vendored
File diff suppressed because one or more lines are too long
30
node_modules/.package-lock.json
generated
vendored
30
node_modules/.package-lock.json
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@ -183,6 +183,20 @@
|
|||||||
"@esbuild/win32-x64": "0.21.5"
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
@ -431,6 +445,20 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
node_modules/.vite/deps/_metadata.json
generated
vendored
6
node_modules/.vite/deps/_metadata.json
generated
vendored
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"hash": "f5c4792c",
|
"hash": "a2c51734",
|
||||||
"configHash": "a1c4c8e0",
|
"configHash": "a1c4c8e0",
|
||||||
"lockfileHash": "4618e5dd",
|
"lockfileHash": "99ec7e3e",
|
||||||
"browserHash": "7960c498",
|
"browserHash": "7e396f40",
|
||||||
"optimized": {},
|
"optimized": {},
|
||||||
"chunks": {}
|
"chunks": {}
|
||||||
}
|
}
|
||||||
3
node_modules/.vite/deps_temp_b6940abf/package.json
generated
vendored
3
node_modules/.vite/deps_temp_b6940abf/package.json
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
22
node_modules/fsevents/LICENSE
generated
vendored
Normal file
22
node_modules/fsevents/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
MIT License
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
83
node_modules/fsevents/README.md
generated
vendored
Normal file
83
node_modules/fsevents/README.md
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# fsevents [](https://nodei.co/npm/fsevents/)
|
||||||
|
|
||||||
|
Native access to MacOS FSEvents in [Node.js](https://nodejs.org/)
|
||||||
|
|
||||||
|
The FSEvents API in MacOS allows applications to register for notifications of
|
||||||
|
changes to a given directory tree. It is a very fast and lightweight alternative
|
||||||
|
to kqueue.
|
||||||
|
|
||||||
|
This is a low-level library. For a cross-platform file watching module that
|
||||||
|
uses fsevents, check out [Chokidar](https://github.com/paulmillr/chokidar).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Supports only **Node.js v8.16 and higher**.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install fsevents
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const fsevents = require('fsevents');
|
||||||
|
const stop = fsevents.watch(__dirname, (path, flags, id) => {
|
||||||
|
const info = fsevents.getInfo(path, flags, id);
|
||||||
|
}); // To start observation
|
||||||
|
stop(); // To end observation
|
||||||
|
```
|
||||||
|
|
||||||
|
The callback passed as the second parameter to `.watch` get's called whenever the operating system detects a
|
||||||
|
a change in the file system. It takes three arguments:
|
||||||
|
|
||||||
|
###### `fsevents.watch(dirname: string, (path: string, flags: number, id: string) => void): () => Promise<undefined>`
|
||||||
|
|
||||||
|
* `path: string` - the item in the filesystem that have been changed
|
||||||
|
* `flags: number` - a numeric value describing what the change was
|
||||||
|
* `id: string` - an unique-id identifying this specific event
|
||||||
|
|
||||||
|
Returns closer callback which when called returns a Promise resolving when the watcher process has been shut down.
|
||||||
|
|
||||||
|
###### `fsevents.getInfo(path: string, flags: number, id: string): FsEventInfo`
|
||||||
|
|
||||||
|
The `getInfo` function takes the `path`, `flags` and `id` arguments and converts those parameters into a structure
|
||||||
|
that is easier to digest to determine what the change was.
|
||||||
|
|
||||||
|
The `FsEventsInfo` has the following shape:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @typedef {'created'|'modified'|'deleted'|'moved'|'root-changed'|'cloned'|'unknown'} FsEventsEvent
|
||||||
|
* @typedef {'file'|'directory'|'symlink'} FsEventsType
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"event": "created", // {FsEventsEvent}
|
||||||
|
"path": "file.txt",
|
||||||
|
"type": "file", // {FsEventsType}
|
||||||
|
"changes": {
|
||||||
|
"inode": true, // Had iNode Meta-Information changed
|
||||||
|
"finder": false, // Had Finder Meta-Data changed
|
||||||
|
"access": false, // Had access permissions changed
|
||||||
|
"xattrs": false // Had xAttributes changed
|
||||||
|
},
|
||||||
|
"flags": 0x100000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- v2.3 supports Apple Silicon ARM CPUs
|
||||||
|
- v2 supports node 8.16+ and reduces package size massively
|
||||||
|
- v1.2.8 supports node 6+
|
||||||
|
- v1.2.7 supports node 4+
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- I'm getting `EBADPLATFORM` `Unsupported platform for fsevents` error.
|
||||||
|
- It's fine, nothing is broken. fsevents is macos-only. Other platforms are skipped. If you want to hide this warning, report a bug to NPM bugtracker asking them to hide ebadplatform warnings by default.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller — see LICENSE file.
|
||||||
|
|
||||||
|
Visit our [GitHub page](https://github.com/fsevents/fsevents) and [NPM Page](https://npmjs.org/package/fsevents)
|
||||||
46
node_modules/fsevents/fsevents.d.ts
generated
vendored
Normal file
46
node_modules/fsevents/fsevents.d.ts
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
declare type Event = "created" | "cloned" | "modified" | "deleted" | "moved" | "root-changed" | "unknown";
|
||||||
|
declare type Type = "file" | "directory" | "symlink";
|
||||||
|
declare type FileChanges = {
|
||||||
|
inode: boolean;
|
||||||
|
finder: boolean;
|
||||||
|
access: boolean;
|
||||||
|
xattrs: boolean;
|
||||||
|
};
|
||||||
|
declare type Info = {
|
||||||
|
event: Event;
|
||||||
|
path: string;
|
||||||
|
type: Type;
|
||||||
|
changes: FileChanges;
|
||||||
|
flags: number;
|
||||||
|
};
|
||||||
|
declare type WatchHandler = (path: string, flags: number, id: string) => void;
|
||||||
|
export declare function watch(path: string, handler: WatchHandler): () => Promise<void>;
|
||||||
|
export declare function watch(path: string, since: number, handler: WatchHandler): () => Promise<void>;
|
||||||
|
export declare function getInfo(path: string, flags: number): Info;
|
||||||
|
export declare const constants: {
|
||||||
|
None: 0x00000000;
|
||||||
|
MustScanSubDirs: 0x00000001;
|
||||||
|
UserDropped: 0x00000002;
|
||||||
|
KernelDropped: 0x00000004;
|
||||||
|
EventIdsWrapped: 0x00000008;
|
||||||
|
HistoryDone: 0x00000010;
|
||||||
|
RootChanged: 0x00000020;
|
||||||
|
Mount: 0x00000040;
|
||||||
|
Unmount: 0x00000080;
|
||||||
|
ItemCreated: 0x00000100;
|
||||||
|
ItemRemoved: 0x00000200;
|
||||||
|
ItemInodeMetaMod: 0x00000400;
|
||||||
|
ItemRenamed: 0x00000800;
|
||||||
|
ItemModified: 0x00001000;
|
||||||
|
ItemFinderInfoMod: 0x00002000;
|
||||||
|
ItemChangeOwner: 0x00004000;
|
||||||
|
ItemXattrMod: 0x00008000;
|
||||||
|
ItemIsFile: 0x00010000;
|
||||||
|
ItemIsDir: 0x00020000;
|
||||||
|
ItemIsSymlink: 0x00040000;
|
||||||
|
ItemIsHardlink: 0x00100000;
|
||||||
|
ItemIsLastHardlink: 0x00200000;
|
||||||
|
OwnEvent: 0x00080000;
|
||||||
|
ItemCloned: 0x00400000;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
82
node_modules/fsevents/fsevents.js
generated
vendored
Normal file
82
node_modules/fsevents/fsevents.js
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
** © 2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
||||||
|
** Licensed under MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint node:true */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (process.platform !== "darwin") {
|
||||||
|
throw new Error(`Module 'fsevents' is not compatible with platform '${process.platform}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Native = require("./fsevents.node");
|
||||||
|
const events = Native.constants;
|
||||||
|
|
||||||
|
function watch(path, since, handler) {
|
||||||
|
if (typeof path !== "string") {
|
||||||
|
throw new TypeError(`fsevents argument 1 must be a string and not a ${typeof path}`);
|
||||||
|
}
|
||||||
|
if ("function" === typeof since && "undefined" === typeof handler) {
|
||||||
|
handler = since;
|
||||||
|
since = Native.flags.SinceNow;
|
||||||
|
}
|
||||||
|
if (typeof since !== "number") {
|
||||||
|
throw new TypeError(`fsevents argument 2 must be a number and not a ${typeof since}`);
|
||||||
|
}
|
||||||
|
if (typeof handler !== "function") {
|
||||||
|
throw new TypeError(`fsevents argument 3 must be a function and not a ${typeof handler}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = Native.start(Native.global, path, since, handler);
|
||||||
|
if (!instance) throw new Error(`could not watch: ${path}`);
|
||||||
|
return () => {
|
||||||
|
const result = instance ? Promise.resolve(instance).then(Native.stop) : Promise.resolve(undefined);
|
||||||
|
instance = undefined;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInfo(path, flags) {
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
flags,
|
||||||
|
event: getEventType(flags),
|
||||||
|
type: getFileType(flags),
|
||||||
|
changes: getFileChanges(flags),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileType(flags) {
|
||||||
|
if (events.ItemIsFile & flags) return "file";
|
||||||
|
if (events.ItemIsDir & flags) return "directory";
|
||||||
|
if (events.ItemIsSymlink & flags) return "symlink";
|
||||||
|
}
|
||||||
|
function anyIsTrue(obj) {
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj[key]) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function getEventType(flags) {
|
||||||
|
if (events.ItemRemoved & flags) return "deleted";
|
||||||
|
if (events.ItemRenamed & flags) return "moved";
|
||||||
|
if (events.ItemCreated & flags) return "created";
|
||||||
|
if (events.ItemModified & flags) return "modified";
|
||||||
|
if (events.RootChanged & flags) return "root-changed";
|
||||||
|
if (events.ItemCloned & flags) return "cloned";
|
||||||
|
if (anyIsTrue(flags)) return "modified";
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
function getFileChanges(flags) {
|
||||||
|
return {
|
||||||
|
inode: !!(events.ItemInodeMetaMod & flags),
|
||||||
|
finder: !!(events.ItemFinderInfoMod & flags),
|
||||||
|
access: !!(events.ItemChangeOwner & flags),
|
||||||
|
xattrs: !!(events.ItemXattrMod & flags),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.watch = watch;
|
||||||
|
exports.getInfo = getInfo;
|
||||||
|
exports.constants = events;
|
||||||
BIN
node_modules/fsevents/fsevents.node
generated
vendored
Executable file
BIN
node_modules/fsevents/fsevents.node
generated
vendored
Executable file
Binary file not shown.
62
node_modules/fsevents/package.json
generated
vendored
Normal file
62
node_modules/fsevents/package.json
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "fsevents",
|
||||||
|
"version": "2.3.2",
|
||||||
|
"description": "Native Access to MacOS FSEvents",
|
||||||
|
"main": "fsevents.js",
|
||||||
|
"types": "fsevents.d.ts",
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"fsevents.d.ts",
|
||||||
|
"fsevents.js",
|
||||||
|
"fsevents.node"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "node-gyp clean && rm -f fsevents.node",
|
||||||
|
"build": "node-gyp clean && rm -f fsevents.node && node-gyp rebuild && node-gyp clean",
|
||||||
|
"test": "/bin/bash ./test.sh 2>/dev/null",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/fsevents/fsevents.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fsevents",
|
||||||
|
"mac"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Philipp Dunkel",
|
||||||
|
"email": "pip@pipobscure.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ben Noordhuis",
|
||||||
|
"email": "info@bnoordhuis.nl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Elan Shankar",
|
||||||
|
"email": "elan.shanker@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Miroslav Bajtoš",
|
||||||
|
"email": "mbajtoss@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Paul Miller",
|
||||||
|
"url": "https://paulmillr.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/fsevents/fsevents/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/fsevents/fsevents",
|
||||||
|
"devDependencies": {
|
||||||
|
"node-gyp": "^6.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
node_modules/vite/node_modules/fsevents/LICENSE
generated
vendored
Normal file
22
node_modules/vite/node_modules/fsevents/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
MIT License
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
89
node_modules/vite/node_modules/fsevents/README.md
generated
vendored
Normal file
89
node_modules/vite/node_modules/fsevents/README.md
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# fsevents
|
||||||
|
|
||||||
|
Native access to MacOS FSEvents in [Node.js](https://nodejs.org/)
|
||||||
|
|
||||||
|
The FSEvents API in MacOS allows applications to register for notifications of
|
||||||
|
changes to a given directory tree. It is a very fast and lightweight alternative
|
||||||
|
to kqueue.
|
||||||
|
|
||||||
|
This is a low-level library. For a cross-platform file watching module that
|
||||||
|
uses fsevents, check out [Chokidar](https://github.com/paulmillr/chokidar).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install fsevents
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports only **Node.js v8.16 and higher**.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const fsevents = require('fsevents');
|
||||||
|
|
||||||
|
// To start observation
|
||||||
|
const stop = fsevents.watch(__dirname, (path, flags, id) => {
|
||||||
|
const info = fsevents.getInfo(path, flags);
|
||||||
|
});
|
||||||
|
|
||||||
|
// To end observation
|
||||||
|
stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important note:** The API behaviour is slightly different from typical JS APIs. The `stop` function **must** be
|
||||||
|
> retrieved and stored somewhere, even if you don't plan to stop the watcher. If you forget it, the garbage collector
|
||||||
|
> will eventually kick in, the watcher will be unregistered, and your callbacks won't be called anymore.
|
||||||
|
|
||||||
|
The callback passed as the second parameter to `.watch` get's called whenever the operating system detects a
|
||||||
|
a change in the file system. It takes three arguments:
|
||||||
|
|
||||||
|
###### `fsevents.watch(dirname: string, (path: string, flags: number, id: string) => void): () => Promise<undefined>`
|
||||||
|
|
||||||
|
* `path: string` - the item in the filesystem that have been changed
|
||||||
|
* `flags: number` - a numeric value describing what the change was
|
||||||
|
* `id: string` - an unique-id identifying this specific event
|
||||||
|
|
||||||
|
Returns closer callback which when called returns a Promise resolving when the watcher process has been shut down.
|
||||||
|
|
||||||
|
###### `fsevents.getInfo(path: string, flags: number, id: string): FsEventInfo`
|
||||||
|
|
||||||
|
The `getInfo` function takes the `path`, `flags` and `id` arguments and converts those parameters into a structure
|
||||||
|
that is easier to digest to determine what the change was.
|
||||||
|
|
||||||
|
The `FsEventsInfo` has the following shape:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @typedef {'created'|'modified'|'deleted'|'moved'|'root-changed'|'cloned'|'unknown'} FsEventsEvent
|
||||||
|
* @typedef {'file'|'directory'|'symlink'} FsEventsType
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"event": "created", // {FsEventsEvent}
|
||||||
|
"path": "file.txt",
|
||||||
|
"type": "file", // {FsEventsType}
|
||||||
|
"changes": {
|
||||||
|
"inode": true, // Had iNode Meta-Information changed
|
||||||
|
"finder": false, // Had Finder Meta-Data changed
|
||||||
|
"access": false, // Had access permissions changed
|
||||||
|
"xattrs": false // Had xAttributes changed
|
||||||
|
},
|
||||||
|
"flags": 0x100000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- v2.3 supports Apple Silicon ARM CPUs
|
||||||
|
- v2 supports node 8.16+ and reduces package size massively
|
||||||
|
- v1.2.8 supports node 6+
|
||||||
|
- v1.2.7 supports node 4+
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- I'm getting `EBADPLATFORM` `Unsupported platform for fsevents` error.
|
||||||
|
- It's fine, nothing is broken. fsevents is macos-only. Other platforms are skipped. If you want to hide this warning, report a bug to NPM bugtracker asking them to hide ebadplatform warnings by default.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License Copyright (C) 2010-2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller — see LICENSE file.
|
||||||
|
|
||||||
|
Visit our [GitHub page](https://github.com/fsevents/fsevents) and [NPM Page](https://npmjs.org/package/fsevents)
|
||||||
46
node_modules/vite/node_modules/fsevents/fsevents.d.ts
generated
vendored
Normal file
46
node_modules/vite/node_modules/fsevents/fsevents.d.ts
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
declare type Event = "created" | "cloned" | "modified" | "deleted" | "moved" | "root-changed" | "unknown";
|
||||||
|
declare type Type = "file" | "directory" | "symlink";
|
||||||
|
declare type FileChanges = {
|
||||||
|
inode: boolean;
|
||||||
|
finder: boolean;
|
||||||
|
access: boolean;
|
||||||
|
xattrs: boolean;
|
||||||
|
};
|
||||||
|
declare type Info = {
|
||||||
|
event: Event;
|
||||||
|
path: string;
|
||||||
|
type: Type;
|
||||||
|
changes: FileChanges;
|
||||||
|
flags: number;
|
||||||
|
};
|
||||||
|
declare type WatchHandler = (path: string, flags: number, id: string) => void;
|
||||||
|
export declare function watch(path: string, handler: WatchHandler): () => Promise<void>;
|
||||||
|
export declare function watch(path: string, since: number, handler: WatchHandler): () => Promise<void>;
|
||||||
|
export declare function getInfo(path: string, flags: number): Info;
|
||||||
|
export declare const constants: {
|
||||||
|
None: 0x00000000;
|
||||||
|
MustScanSubDirs: 0x00000001;
|
||||||
|
UserDropped: 0x00000002;
|
||||||
|
KernelDropped: 0x00000004;
|
||||||
|
EventIdsWrapped: 0x00000008;
|
||||||
|
HistoryDone: 0x00000010;
|
||||||
|
RootChanged: 0x00000020;
|
||||||
|
Mount: 0x00000040;
|
||||||
|
Unmount: 0x00000080;
|
||||||
|
ItemCreated: 0x00000100;
|
||||||
|
ItemRemoved: 0x00000200;
|
||||||
|
ItemInodeMetaMod: 0x00000400;
|
||||||
|
ItemRenamed: 0x00000800;
|
||||||
|
ItemModified: 0x00001000;
|
||||||
|
ItemFinderInfoMod: 0x00002000;
|
||||||
|
ItemChangeOwner: 0x00004000;
|
||||||
|
ItemXattrMod: 0x00008000;
|
||||||
|
ItemIsFile: 0x00010000;
|
||||||
|
ItemIsDir: 0x00020000;
|
||||||
|
ItemIsSymlink: 0x00040000;
|
||||||
|
ItemIsHardlink: 0x00100000;
|
||||||
|
ItemIsLastHardlink: 0x00200000;
|
||||||
|
OwnEvent: 0x00080000;
|
||||||
|
ItemCloned: 0x00400000;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
83
node_modules/vite/node_modules/fsevents/fsevents.js
generated
vendored
Normal file
83
node_modules/vite/node_modules/fsevents/fsevents.js
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
** © 2020 by Philipp Dunkel, Ben Noordhuis, Elan Shankar, Paul Miller
|
||||||
|
** Licensed under MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint node:true */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (process.platform !== "darwin") {
|
||||||
|
throw new Error(`Module 'fsevents' is not compatible with platform '${process.platform}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Native = require("./fsevents.node");
|
||||||
|
const events = Native.constants;
|
||||||
|
|
||||||
|
function watch(path, since, handler) {
|
||||||
|
if (typeof path !== "string") {
|
||||||
|
throw new TypeError(`fsevents argument 1 must be a string and not a ${typeof path}`);
|
||||||
|
}
|
||||||
|
if ("function" === typeof since && "undefined" === typeof handler) {
|
||||||
|
handler = since;
|
||||||
|
since = Native.flags.SinceNow;
|
||||||
|
}
|
||||||
|
if (typeof since !== "number") {
|
||||||
|
throw new TypeError(`fsevents argument 2 must be a number and not a ${typeof since}`);
|
||||||
|
}
|
||||||
|
if (typeof handler !== "function") {
|
||||||
|
throw new TypeError(`fsevents argument 3 must be a function and not a ${typeof handler}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = Native.start(Native.global, path, since, handler);
|
||||||
|
if (!instance) throw new Error(`could not watch: ${path}`);
|
||||||
|
return () => {
|
||||||
|
const result = instance ? Promise.resolve(instance).then(Native.stop) : Promise.resolve(undefined);
|
||||||
|
instance = undefined;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInfo(path, flags) {
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
flags,
|
||||||
|
event: getEventType(flags),
|
||||||
|
type: getFileType(flags),
|
||||||
|
changes: getFileChanges(flags),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileType(flags) {
|
||||||
|
if (events.ItemIsFile & flags) return "file";
|
||||||
|
if (events.ItemIsDir & flags) return "directory";
|
||||||
|
if (events.MustScanSubDirs & flags) return "directory";
|
||||||
|
if (events.ItemIsSymlink & flags) return "symlink";
|
||||||
|
}
|
||||||
|
function anyIsTrue(obj) {
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj[key]) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function getEventType(flags) {
|
||||||
|
if (events.ItemRemoved & flags) return "deleted";
|
||||||
|
if (events.ItemRenamed & flags) return "moved";
|
||||||
|
if (events.ItemCreated & flags) return "created";
|
||||||
|
if (events.ItemModified & flags) return "modified";
|
||||||
|
if (events.RootChanged & flags) return "root-changed";
|
||||||
|
if (events.ItemCloned & flags) return "cloned";
|
||||||
|
if (anyIsTrue(flags)) return "modified";
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
function getFileChanges(flags) {
|
||||||
|
return {
|
||||||
|
inode: !!(events.ItemInodeMetaMod & flags),
|
||||||
|
finder: !!(events.ItemFinderInfoMod & flags),
|
||||||
|
access: !!(events.ItemChangeOwner & flags),
|
||||||
|
xattrs: !!(events.ItemXattrMod & flags),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.watch = watch;
|
||||||
|
exports.getInfo = getInfo;
|
||||||
|
exports.constants = events;
|
||||||
BIN
node_modules/vite/node_modules/fsevents/fsevents.node
generated
vendored
Executable file
BIN
node_modules/vite/node_modules/fsevents/fsevents.node
generated
vendored
Executable file
Binary file not shown.
62
node_modules/vite/node_modules/fsevents/package.json
generated
vendored
Normal file
62
node_modules/vite/node_modules/fsevents/package.json
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "fsevents",
|
||||||
|
"version": "2.3.3",
|
||||||
|
"description": "Native Access to MacOS FSEvents",
|
||||||
|
"main": "fsevents.js",
|
||||||
|
"types": "fsevents.d.ts",
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"fsevents.d.ts",
|
||||||
|
"fsevents.js",
|
||||||
|
"fsevents.node"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "node-gyp clean && rm -f fsevents.node",
|
||||||
|
"build": "node-gyp clean && rm -f fsevents.node && node-gyp rebuild && node-gyp clean",
|
||||||
|
"test": "/bin/bash ./test.sh 2>/dev/null",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/fsevents/fsevents.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fsevents",
|
||||||
|
"mac"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Philipp Dunkel",
|
||||||
|
"email": "pip@pipobscure.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ben Noordhuis",
|
||||||
|
"email": "info@bnoordhuis.nl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Elan Shankar",
|
||||||
|
"email": "elan.shanker@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Miroslav Bajtoš",
|
||||||
|
"email": "mbajtoss@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Paul Miller",
|
||||||
|
"url": "https://paulmillr.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/fsevents/fsevents/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/fsevents/fsevents",
|
||||||
|
"devDependencies": {
|
||||||
|
"node-gyp": "^9.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@web/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"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/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/base.js",
|
"main": "dist/base.js",
|
||||||
"module": "dist/base.js",
|
"module": "dist/base.js",
|
||||||
|
|||||||
59
src/form.js
59
src/form.js
@ -4,10 +4,23 @@ import { HTTP } from './http.js'
|
|||||||
Component.register('AutoForm', container => {
|
Component.register('AutoForm', container => {
|
||||||
if (!container.state.schema) container.state.schema = []
|
if (!container.state.schema) container.state.schema = []
|
||||||
container.vertical = container.hasAttribute('vertical')
|
container.vertical = container.hasAttribute('vertical')
|
||||||
|
container.inline = container.hasAttribute('inline')
|
||||||
container.request = { method: 'POST' }
|
container.request = { method: 'POST' }
|
||||||
container.response = {}
|
container.response = {}
|
||||||
container.result = null
|
container.result = null
|
||||||
|
|
||||||
|
// 立即初始化数据代理,避免异步延迟导致 Select 等组件初始化时找不到值而重置
|
||||||
|
if (!container.data || !container.data.__watch) {
|
||||||
container.data = NewState(container.data || {})
|
container.data = NewState(container.data || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
container.data.__watch('*', () => {
|
||||||
|
if (container.inline) {
|
||||||
|
const dt = container.closest('DataTable')
|
||||||
|
if (dt && dt.refresh) dt.refresh()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
container.form = $(container, 'form')
|
container.form = $(container, 'form')
|
||||||
container.submit = (opt = {}) => {
|
container.submit = (opt = {}) => {
|
||||||
if (!container.form.reportValidity()) return globalThis.UI?.toast?.('{#verify failed#}', { type: 'danger' })
|
if (!container.form.reportValidity()) return globalThis.UI?.toast?.('{#verify failed#}', { type: 'danger' })
|
||||||
@ -28,33 +41,42 @@ Component.register('AutoForm', container => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, Util.makeDom(/*html*/`
|
}, Util.makeDom(/*html*/`
|
||||||
<div>
|
<div $class="'auto-form-root' + (this.inline ? ' auto-form-inline min-h-100 w-100' : '')">
|
||||||
<form $class="align-items-center \${this.vertical?'':'auto-grid-form'}" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
<form $class="'h-100 w-100 d-flex ' + (this.inline ? 'align-items-center' : (this.vertical ? 'flex-column' : 'auto-grid-form'))" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
||||||
<div $each="this.state.schema || []" style="display:contents">
|
<div $each="this.state.schema || []" style="display:contents">
|
||||||
<label $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
<label $if="!this.inline" $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||||
<div control-wrapper class="mb-3">
|
<div control-wrapper $class="this.inline ? 'flex-grow-1 h-100 d-flex align-items-center' : 'mb-3'">
|
||||||
<input $if="['text', 'password', 'email', 'number', 'date', 'datetime', 'file'].includes(item.type)" $name="item.name" class="form-control" $type="item.type" $.="item.setting" $bind="this.data[item.name]">
|
<input $if="['text', 'password', 'email', 'number', 'date', 'datetime', 'file'].includes(item.type)" $name="item.name" class="form-control" $type="item.type" $.="item.setting || {}" $bind="this.data[item.name]" $class="item.type === 'number' ? 'text-end' : ''">
|
||||||
<select $if="item.type === 'select'" $name="item.name" class="form-select" $.="item.setting" $bind="this.data[item.name]">
|
<select $if="item.type === 'select'" $name="item.name" class="form-select" $.="item.setting || {}" $bind="this.data[item.name]">
|
||||||
<option value="" $if="item.placeholder" $text="item.placeholder" disabled selected></option>
|
<option value="" $if="item.placeholder" $text="item.placeholder" disabled></option>
|
||||||
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
||||||
</select>
|
</select>
|
||||||
<div $if="['checkbox', 'radio'].includes(item.type)" >
|
<div $if="['checkbox', 'radio'].includes(item.type)" $class="this.inline ? 'h-100 d-flex align-items-center px-2 gap-3 justify-content-center w-100' : ''" style="white-space:nowrap">
|
||||||
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="form-check\${item.vertical ? '' : ' form-check-inline'}">
|
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="'form-check' + (item.vertical ? '' : ' form-check-inline') + ' mb-0 d-flex align-items-center p-0'">
|
||||||
<input $name="item.name" class="form-check-input" $type="item.type" $.="item.setting" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
<input $name="item.name" class="form-check-input me-1" $type="item.type" $.="item.setting || {}" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
||||||
<span $text="option" class="form-check-label"></span>
|
<span $if="!this.inline || (item.options && item.options.length > 0)" $text="option" class="form-check-label"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div $if="item.type==='switch'" class="form-check form-switch fs-4"><input $name="item.name" class="form-check-input my-0" type="checkbox" $bind="this.data[item.name]"></div>
|
<div $if="item.type==='switch'" class="form-check form-switch fs-4 h-100 d-flex align-items-center px-2 m-0 justify-content-center w-100" style="padding-left:0">
|
||||||
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting" $bind="this.data[item.name]"></textarea>
|
<input $name="item.name" class="form-check-input m-0" type="checkbox" style="cursor:pointer" $bind="this.data[item.name]">
|
||||||
|
</div>
|
||||||
|
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting || {}" $bind="this.data[item.name]"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end align-items-baseline gap-3 mt-3" style="grid-column:1/-1">
|
<div $if="!this.inline" class="d-flex justify-content-end align-items-baseline gap-3 mt-3" style="grid-column:1/-1">
|
||||||
<div slot-id="actions"></div>
|
<div slot-id="actions"></div>
|
||||||
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
`), Util.makeDom(/*html*/`<style>@media (min-width: 576px) { .auto-grid-form {display: grid;grid-template-columns: max-content 1fr} .auto-grid-form .col-form-label {text-align: right; margin-bottom: 1rem;padding-right: 1rem;max-width: 200px} }</style>`))
|
`), Util.makeDom(/*html*/`<style>
|
||||||
|
@media (min-width: 576px) { .auto-grid-form {display: grid;grid-template-columns: max-content 1fr} .auto-grid-form .col-form-label {text-align: right; margin-bottom: 1rem;padding-right: 1rem;max-width: 200px} }
|
||||||
|
.auto-form-inline { background-color: var(--bs-body-bg); }
|
||||||
|
.auto-form-inline.min-h-100 { min-height: 100%; height: auto; }
|
||||||
|
.auto-form-inline .form-control, .auto-form-inline .form-select { border-radius: 0; border: none; background-color: transparent !important; min-height: 100%; height: auto; width: 100%; padding: 0 8px; box-shadow: none; color: inherit; }
|
||||||
|
.auto-form-inline .form-select { background-position: right 4px center; background-size: 12px 10px; padding-right: 20px; }
|
||||||
|
.auto-form-inline textarea.form-control { padding: 8px; min-height: 100px; height: auto; }
|
||||||
|
</style>`))
|
||||||
|
|
||||||
const _pendingAutoFormComponents = []
|
const _pendingAutoFormComponents = []
|
||||||
export const AutoForm = {
|
export const AutoForm = {
|
||||||
@ -67,10 +89,7 @@ export const AutoForm = {
|
|||||||
_addAutoFormComponent: name => {
|
_addAutoFormComponent: name => {
|
||||||
const template = Component.getTemplate('AutoForm')
|
const template = Component.getTemplate('AutoForm')
|
||||||
if (template) {
|
if (template) {
|
||||||
$(template.content, '[control-wrapper]')?.appendChild(Util.makeDom(`<${name} $if="item.type.toUpperCase() === '${name.toUpperCase()}'" $name="item.name" $.="item.setting" $bind="this.data[item.name]"></${name}>`))
|
$(template.content, '[control-wrapper]')?.appendChild(Util.makeDom(`<${name} $if="item.type.toUpperCase() === '${name.toUpperCase()}'" $name="item.name" $.="item.setting || {}" $bind="this.data[item.name]"></${name}>`))
|
||||||
} else if (document.readyState !== 'loading') {
|
|
||||||
// If template still missing after DOM ready, something is wrong
|
|
||||||
console.error('AutoForm template not found during registration of', name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +99,7 @@ if (typeof document !== 'undefined') {
|
|||||||
_pendingAutoFormComponents.forEach(name => AutoForm._addAutoFormComponent(name))
|
_pendingAutoFormComponents.forEach(name => AutoForm._addAutoFormComponent(name))
|
||||||
_pendingAutoFormComponents.length = 0
|
_pendingAutoFormComponents.length = 0
|
||||||
}
|
}
|
||||||
if (document.readyState !== 'loading') setTimeout(initAutoForm, 100) // Wait slightly for component templates to be added to body
|
if (document.readyState !== 'loading') setTimeout(initAutoForm, 100)
|
||||||
else document.addEventListener('DOMContentLoaded', () => setTimeout(initAutoForm, 100), true)
|
else document.addEventListener('DOMContentLoaded', () => setTimeout(initAutoForm, 100), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,19 +28,32 @@ if (typeof document !== 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.register('Resizer', container => {
|
Component.register('Resizer', container => {
|
||||||
const isVertical = container.hasAttribute('vertical')
|
container.isVertical = container.hasAttribute('vertical')
|
||||||
const min = parseInt(container.getAttribute('min')) || 10
|
const min = parseInt(container.getAttribute('min')) || 10
|
||||||
const max = parseInt(container.getAttribute('max')) || 1000
|
const max = parseInt(container.getAttribute('max')) || 1000
|
||||||
const target = container.target || container.previousElementSibling
|
const target = container.target || container.previousElementSibling
|
||||||
|
container.addEventListener('bind', e => {
|
||||||
|
if (e.detail !== undefined && e.detail !== null) {
|
||||||
|
target.style[container.isVertical ? 'height' : 'width'] = e.detail + 'px'
|
||||||
|
}
|
||||||
|
})
|
||||||
const getSize = (startSize, w, h) => {
|
const getSize = (startSize, w, h) => {
|
||||||
const newSize = startSize + (isVertical ? h : w)
|
const newSize = startSize + (container.isVertical ? h : w)
|
||||||
return newSize < min ? min : newSize > max ? max : newSize
|
return newSize < min ? min : newSize > max ? max : newSize
|
||||||
}
|
}
|
||||||
container.addEventListener('mousedown', event => {
|
container.addEventListener('mousedown', event => {
|
||||||
const startSize = isVertical ? target.offsetHeight : target.offsetWidth
|
const startSize = container.isVertical ? target.offsetHeight : target.offsetWidth
|
||||||
MouseMover.start(event, {
|
MouseMover.start(event, {
|
||||||
onmousemove: ({ w, h }) => target.style[isVertical ? 'height' : 'width'] = getSize(startSize, w, h) + 'px',
|
onmousemove: ({ w, h }) => {
|
||||||
onmouseup: ({ w, h }) => container.dispatchEvent(new CustomEvent('resize', { detail: { oldSize: startSize, newSize: getSize(startSize, w, h) }, bubbles: false })),
|
const newSize = getSize(startSize, w, h)
|
||||||
|
target.style[container.isVertical ? 'height' : 'width'] = newSize + 'px'
|
||||||
|
container.dispatchEvent(new CustomEvent('resizing', { detail: { oldSize: startSize, newSize }, bubbles: false }))
|
||||||
|
},
|
||||||
|
onmouseup: ({ w, h }) => {
|
||||||
|
const newSize = getSize(startSize, w, h)
|
||||||
|
container.dispatchEvent(new CustomEvent('resize', { detail: { oldSize: startSize, newSize }, bubbles: false }))
|
||||||
|
container.dispatchEvent(new CustomEvent('change', { detail: newSize, bubbles: false }))
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, Util.makeDom(/*html*/`
|
}, Util.makeDom(/*html*/`
|
||||||
|
|||||||
165
src/list.js
165
src/list.js
@ -1,23 +1,63 @@
|
|||||||
import { Component, NewState, Util, Hash } from '@web/state'
|
import { Component, NewState, Util, Hash } from '@web/state'
|
||||||
|
|
||||||
export const FastListComponent = Component.register('FastList', container => {
|
export const VirtualScroll = () => {
|
||||||
const itemHeights = new Map()
|
const itemHeights = new Map()
|
||||||
const groupHeights = new Map()
|
const groupHeights = new Map()
|
||||||
let groupItemCount = 1
|
let groupItemCount = 1
|
||||||
container.state.renderedList = []
|
|
||||||
const avg = Util.newAvg()
|
const avg = Util.newAvg()
|
||||||
let containerPaddingTop = 0
|
|
||||||
let containerRowGap = 0
|
let padTop = 0
|
||||||
|
let rowGap = 0
|
||||||
let topMargin = 0
|
let topMargin = 0
|
||||||
let itemMarginTop = null
|
let itemMarginTop = null
|
||||||
let itemMarginBottom = null
|
let itemMarginBottom = null
|
||||||
let listStartIndex = 0
|
|
||||||
let visibleStartIndex = 0
|
|
||||||
let visibleCount = 10
|
|
||||||
let listInited = false
|
let listInited = false
|
||||||
|
|
||||||
container.onItemUpdate = (index, node) => {
|
return {
|
||||||
const absoluteIndex = index + listStartIndex
|
// 核心1:重置与首屏预渲染
|
||||||
|
reset: (list, container) => {
|
||||||
|
listInited = false
|
||||||
|
itemHeights.clear()
|
||||||
|
groupHeights.clear()
|
||||||
|
avg.clear()
|
||||||
|
topMargin = 0
|
||||||
|
itemMarginTop = null
|
||||||
|
itemMarginBottom = null
|
||||||
|
|
||||||
|
if (!list?.length) return []
|
||||||
|
|
||||||
|
const size = list.length
|
||||||
|
groupItemCount = Math.ceil(Math.sqrt(size)) || 10
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(container)
|
||||||
|
padTop = parseFloat(style.paddingTop) || 0
|
||||||
|
rowGap = parseFloat(style.rowGap) || 0
|
||||||
|
|
||||||
|
return list.slice(0, Math.min(30, size))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 核心2:无痛初始化缓存 (通过 rAF 调用)
|
||||||
|
init: (list, refreshCallback) => {
|
||||||
|
if (listInited) return
|
||||||
|
const size = list.length
|
||||||
|
const defaultHeight = avg.get() || 32
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight)
|
||||||
|
}
|
||||||
|
for (let i = 0; i < size; i += groupItemCount) {
|
||||||
|
let sum = 0
|
||||||
|
for (let j = i; j < Math.min(i + groupItemCount, size); j++) {
|
||||||
|
sum += itemHeights.get(j)
|
||||||
|
}
|
||||||
|
groupHeights.set(i, sum)
|
||||||
|
}
|
||||||
|
listInited = true
|
||||||
|
refreshCallback()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 核心3:原汁原味的极速高度更新
|
||||||
|
update: (absoluteIndex, node) => {
|
||||||
if (itemMarginTop === null) {
|
if (itemMarginTop === null) {
|
||||||
const style = window.getComputedStyle(node)
|
const style = window.getComputedStyle(node)
|
||||||
itemMarginTop = parseFloat(style.marginTop) || 0
|
itemMarginTop = parseFloat(style.marginTop) || 0
|
||||||
@ -25,8 +65,9 @@ export const FastListComponent = Component.register('FastList', container => {
|
|||||||
}
|
}
|
||||||
if (absoluteIndex === 0) topMargin = itemMarginTop
|
if (absoluteIndex === 0) topMargin = itemMarginTop
|
||||||
|
|
||||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap
|
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap
|
||||||
const oldHeight = itemHeights.get(absoluteIndex)
|
const oldHeight = itemHeights.get(absoluteIndex)
|
||||||
|
|
||||||
if (newHeight !== oldHeight) {
|
if (newHeight !== oldHeight) {
|
||||||
itemHeights.set(absoluteIndex, newHeight)
|
itemHeights.set(absoluteIndex, newHeight)
|
||||||
avg.add(newHeight)
|
avg.add(newHeight)
|
||||||
@ -34,39 +75,41 @@ export const FastListComponent = Component.register('FastList', container => {
|
|||||||
const groupIndex = absoluteIndex - (absoluteIndex % groupItemCount)
|
const groupIndex = absoluteIndex - (absoluteIndex % groupItemCount)
|
||||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset)
|
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
container.refresh = () => {
|
// 核心4:原汁原味的状态机滚动计算
|
||||||
if (!listInited) return
|
calc: (container, list) => {
|
||||||
visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32))
|
if (!listInited || !list) return null
|
||||||
let list = []
|
|
||||||
const fullList = container.state.list
|
const size = list.length
|
||||||
const fullSize = fullList.length
|
const visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32))
|
||||||
let i = 0
|
|
||||||
let prev = containerPaddingTop + topMargin + containerRowGap
|
let prev = padTop + topMargin + rowGap
|
||||||
let post = 0
|
let post = 0
|
||||||
let starus = 0
|
let status = 0
|
||||||
for (i = 0; i < fullSize; i++) {
|
let listStartIndex = 0
|
||||||
if (starus === 0) {
|
let listEndIndex = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
if (status === 0) {
|
||||||
const gh = groupHeights.get(i)
|
const gh = groupHeights.get(i)
|
||||||
if (gh && prev + gh < container.scrollTop) {
|
if (gh && prev + gh < container.scrollTop) {
|
||||||
prev += gh
|
prev += gh
|
||||||
i += Math.min(groupItemCount, fullSize - i) - 1
|
i += Math.min(groupItemCount, size - i) - 1
|
||||||
} else {
|
} else {
|
||||||
const ih = itemHeights.get(i)
|
const ih = itemHeights.get(i)
|
||||||
if (prev + ih < container.scrollTop) {
|
if (prev + ih < container.scrollTop) {
|
||||||
prev += ih
|
prev += ih
|
||||||
} else {
|
} else {
|
||||||
starus = 1
|
status = 1
|
||||||
visibleStartIndex = Math.max(0, i)
|
let visibleStartIndex = Math.max(0, i)
|
||||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount)
|
listStartIndex = Math.max(0, visibleStartIndex - visibleCount)
|
||||||
const listEndIndex = Math.min(listStartIndex + visibleCount * 3, fullSize)
|
listEndIndex = Math.min(listStartIndex + visibleCount * 3, size)
|
||||||
i = listEndIndex - 1
|
i = listEndIndex - 1
|
||||||
list = fullList.slice(listStartIndex, listEndIndex)
|
|
||||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j)
|
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (starus === 1) {
|
} else if (status === 1) {
|
||||||
const gh = groupHeights.get(i)
|
const gh = groupHeights.get(i)
|
||||||
if (gh) {
|
if (gh) {
|
||||||
post += gh
|
post += gh
|
||||||
@ -76,42 +119,42 @@ export const FastListComponent = Component.register('FastList', container => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container.state.prevHeight = prev - containerPaddingTop - topMargin - containerRowGap
|
|
||||||
container.state.postHeight = post
|
|
||||||
container.state.renderedList = list
|
|
||||||
}
|
|
||||||
container.state.__watch('list', list => {
|
|
||||||
listInited = false
|
|
||||||
itemHeights.clear()
|
|
||||||
groupHeights.clear()
|
|
||||||
avg.clear()
|
|
||||||
topMargin = 0
|
|
||||||
itemMarginTop = null
|
|
||||||
itemMarginBottom = null
|
|
||||||
if (!list?.length) return
|
|
||||||
|
|
||||||
const listSize = list.length || 0
|
return {
|
||||||
groupItemCount = Math.ceil(Math.sqrt(listSize)) || 10
|
prevHeight: prev - padTop - topMargin - rowGap,
|
||||||
const style = window.getComputedStyle(container)
|
postHeight: post,
|
||||||
containerPaddingTop = parseFloat(style.paddingTop) || 0
|
renderedList: list.slice(listStartIndex, listEndIndex),
|
||||||
containerRowGap = parseFloat(style.rowGap) || 0
|
listStartIndex
|
||||||
container.state.renderedList = list.slice(0, Math.min(30, listSize)) || []
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FastListComponent = Component.register('FastList', container => {
|
||||||
|
const vs = VirtualScroll()
|
||||||
|
container.state.renderedList = []
|
||||||
|
|
||||||
|
container.onItemUpdate = (index, node) => {
|
||||||
|
vs.update(index + (container.state._listStartIndex || 0), node)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.refresh = () => {
|
||||||
|
const res = vs.calc(container, container.state.list)
|
||||||
|
if (res) {
|
||||||
|
container.state.prevHeight = res.prevHeight
|
||||||
|
container.state.postHeight = res.postHeight
|
||||||
|
container.state._listStartIndex = res.listStartIndex
|
||||||
|
container.state.renderedList = res.renderedList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.state.__watch('list', list => {
|
||||||
|
container.state._listStartIndex = 0
|
||||||
|
container.state.renderedList = vs.reset(list, container) || []
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (listInited || container.state.list !== list) return
|
if (container.state.list !== list) return
|
||||||
const defaultHeight = avg.get() || 32
|
vs.init(list, container.refresh)
|
||||||
for (let i = 0; i < listSize; i++) {
|
|
||||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight)
|
|
||||||
}
|
|
||||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
|
||||||
let sum = 0
|
|
||||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
|
||||||
sum += itemHeights.get(j)
|
|
||||||
}
|
|
||||||
groupHeights.set(i, sum)
|
|
||||||
}
|
|
||||||
listInited = true
|
|
||||||
container.refresh()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ Component.register('Modal', container => {
|
|||||||
}, Util.makeDom(/*html*/`
|
}, Util.makeDom(/*html*/`
|
||||||
<div class="modal fade" data-bs-backdrop="static">
|
<div class="modal fade" data-bs-backdrop="static">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div $class="modal-content text-bg-\${this.state?.type || 'body'}">
|
<div $class="modal-content text-bg-\${this.state?.type || 'primary'}">
|
||||||
<div slot-id="header" class="modal-header">
|
<div slot-id="header" class="modal-header">
|
||||||
<h6 class="modal-title" $text="this.state?.title"></h6>
|
<h6 class="modal-title" $text="this.state?.title"></h6>
|
||||||
<button type="button" class="btn btn-link ms-2 bi bi-x-lg link-reset" style="color:inherit" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn btn-link ms-2 bi bi-x-lg link-reset" style="color:inherit" data-bs-dismiss="modal"></button>
|
||||||
@ -30,7 +30,7 @@ Component.register('Modal', container => {
|
|||||||
Component.register('Dialog', Component.getSetupFunction('Modal'), Util.makeDom(/*html*/`
|
Component.register('Dialog', Component.getSetupFunction('Modal'), Util.makeDom(/*html*/`
|
||||||
<div class="modal fade" data-bs-backdrop="static">
|
<div class="modal fade" data-bs-backdrop="static">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div $class="modal-content text-bg-\${this.state?.type || 'body'}">
|
<div $class="modal-content text-bg-\${this.state?.type || 'primary'}">
|
||||||
<div $if="this.state?.title" class="modal-header" $text="this.state?.title"></div>
|
<div $if="this.state?.title" class="modal-header" $text="this.state?.title"></div>
|
||||||
<div slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
<div slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -84,7 +84,7 @@ Component.register('Toast', container => {
|
|||||||
})
|
})
|
||||||
}, Util.makeDom(/*html*/`
|
}, Util.makeDom(/*html*/`
|
||||||
<div class="toast align-items-center border-0 m-1">
|
<div class="toast align-items-center border-0 m-1">
|
||||||
<div $class="toast-body p-3 text-bg-\${this.state?.type}">
|
<div $class="toast-body rounded p-3 text-bg-\${this.state?.type}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<span style="white-space:pre-wrap" class="fs-6" $text="this.state?.message"></span>
|
<span style="white-space:pre-wrap" class="fs-6" $text="this.state?.message"></span>
|
||||||
@ -102,7 +102,7 @@ Component.register('Toast', container => {
|
|||||||
UI.toast = function (message, options = {}) {
|
UI.toast = function (message, options = {}) {
|
||||||
const delay = options.delay ?? 5000
|
const delay = options.delay ?? 5000
|
||||||
const t = document.createElement('Toast')
|
const t = document.createElement('Toast')
|
||||||
t.state = { delay, left: delay ? delay / 1000 : undefined, type: options.type || 'body', message, buttons: options.buttons || [] }
|
t.state = { delay, left: delay ? delay / 1000 : undefined, type: options.type || 'primary', message, buttons: options.buttons || [] }
|
||||||
$(`[toast-container="${options.container || 'default'}"]`).appendChild(t)
|
$(`[toast-container="${options.container || 'default'}"]`).appendChild(t)
|
||||||
Promise.resolve().then(() => t.show())
|
Promise.resolve().then(() => t.show())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user