release: v1.0.2
This commit is contained in:
parent
58081348ab
commit
36f39bfa08
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,5 +1,20 @@
|
||||
# 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)
|
||||
- **Perf**: 优化 `FastList` 渲染逻辑,消除 Layout Thrashing,提升复杂列表(GroupedList/Tree)滚动性能达 10 倍以上。
|
||||
- **Refactor**: 采用 `requestAnimationFrame` 优化初始化高度计算,减少闪烁。
|
||||
|
||||
295
dist/base.js
vendored
295
dist/base.js
vendored
@ -107,7 +107,7 @@ Component.register("Modal", (container) => {
|
||||
`
|
||||
<div class="modal fade" data-bs-backdrop="static">
|
||||
<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">
|
||||
<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>
|
||||
@ -124,7 +124,7 @@ Component.register("Dialog", Component.getSetupFunction("Modal"), Util.makeDom(
|
||||
`
|
||||
<div class="modal fade" data-bs-backdrop="static">
|
||||
<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 slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
||||
<div class="modal-footer">
|
||||
@ -181,7 +181,7 @@ Component.register("Toast", (container) => {
|
||||
/*html*/
|
||||
`
|
||||
<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="flex-grow-1">
|
||||
<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 = {}) {
|
||||
const delay = options.delay ?? 5e3;
|
||||
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);
|
||||
Promise.resolve().then(() => t.show());
|
||||
};
|
||||
@ -212,10 +212,19 @@ UI$1.toastConfirm = function(message, options = {}) {
|
||||
Component.register("AutoForm", (container) => {
|
||||
if (!container.state.schema) container.state.schema = [];
|
||||
container.vertical = container.hasAttribute("vertical");
|
||||
container.inline = container.hasAttribute("inline");
|
||||
container.request = { method: "POST" };
|
||||
container.response = {};
|
||||
container.result = null;
|
||||
container.data = NewState(container.data || {});
|
||||
if (!container.data || !container.data.__watch) {
|
||||
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.submit = (opt = {}) => {
|
||||
var _a, _b;
|
||||
@ -240,27 +249,29 @@ Component.register("AutoForm", (container) => {
|
||||
}, Util.makeDom(
|
||||
/*html*/
|
||||
`
|
||||
<div>
|
||||
<form $class="align-items-center \${this.vertical?'':'auto-grid-form'}" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
||||
<div $class="'auto-form-root' + (this.inline ? ' auto-form-inline min-h-100 w-100' : '')">
|
||||
<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">
|
||||
<label $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||
<div control-wrapper class="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]">
|
||||
<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>
|
||||
<label $if="!this.inline" $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||
<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]" $class="item.type === 'number' ? 'text-end' : ''">
|
||||
<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></option>
|
||||
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
||||
</select>
|
||||
<div $if="['checkbox', 'radio'].includes(item.type)" >
|
||||
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="form-check\${item.vertical ? '' : ' form-check-inline'}">
|
||||
<input $name="item.name" class="form-check-input" $type="item.type" $.="item.setting" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
||||
<span $text="option" class="form-check-label"></span>
|
||||
<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') + ' mb-0 d-flex align-items-center p-0'">
|
||||
<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 $if="!this.inline || (item.options && item.options.length > 0)" $text="option" class="form-check-label"></span>
|
||||
</label>
|
||||
</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>
|
||||
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting" $bind="this.data[item.name]"></textarea>
|
||||
<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">
|
||||
<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 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>
|
||||
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
||||
</div>
|
||||
@ -269,7 +280,14 @@ Component.register("AutoForm", (container) => {
|
||||
`
|
||||
), 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>`
|
||||
`<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 AutoForm = {
|
||||
@ -283,9 +301,7 @@ const AutoForm = {
|
||||
var _a;
|
||||
const template = Component.getTemplate("AutoForm");
|
||||
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}>`));
|
||||
} else if (document.readyState !== "loading") {
|
||||
console.error("AutoForm template not found during registration of", 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}>`));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -337,113 +353,140 @@ Component.register("TagsInput", (container) => {
|
||||
`<style>TagsInput button:focus {background-color:var(--bs-btn-hover-bg);color:var(--bs-btn-hover-color)}</style>`
|
||||
));
|
||||
AutoForm.register("TagsInput");
|
||||
const FastListComponent = Component.register("FastList", (container) => {
|
||||
const VirtualScroll = () => {
|
||||
const itemHeights = /* @__PURE__ */ new Map();
|
||||
const groupHeights = /* @__PURE__ */ new Map();
|
||||
let groupItemCount = 1;
|
||||
container.state.renderedList = [];
|
||||
const avg = Util.newAvg();
|
||||
let containerPaddingTop = 0;
|
||||
let containerRowGap = 0;
|
||||
let padTop = 0;
|
||||
let rowGap = 0;
|
||||
let topMargin = 0;
|
||||
let itemMarginTop = null;
|
||||
let itemMarginBottom = null;
|
||||
let listStartIndex = 0;
|
||||
let visibleStartIndex = 0;
|
||||
let visibleCount = 10;
|
||||
let listInited = false;
|
||||
container.onItemUpdate = (index2, node) => {
|
||||
const absoluteIndex = index2 + listStartIndex;
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node);
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0;
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop;
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap;
|
||||
const oldHeight = itemHeights.get(absoluteIndex);
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight);
|
||||
avg.add(newHeight);
|
||||
const offset = newHeight - (oldHeight || 0);
|
||||
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
|
||||
}
|
||||
};
|
||||
container.refresh = () => {
|
||||
if (!listInited) return;
|
||||
visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32));
|
||||
let list = [];
|
||||
const fullList = container.state.list;
|
||||
const fullSize = fullList.length;
|
||||
let i = 0;
|
||||
let prev = containerPaddingTop + topMargin + containerRowGap;
|
||||
let post = 0;
|
||||
let starus = 0;
|
||||
for (i = 0; i < fullSize; i++) {
|
||||
if (starus === 0) {
|
||||
const gh = groupHeights.get(i);
|
||||
if (gh && prev + gh < container.scrollTop) {
|
||||
prev += gh;
|
||||
i += Math.min(groupItemCount, fullSize - i) - 1;
|
||||
} else {
|
||||
const ih = itemHeights.get(i);
|
||||
if (prev + ih < container.scrollTop) {
|
||||
prev += ih;
|
||||
} else {
|
||||
starus = 1;
|
||||
visibleStartIndex = Math.max(0, i);
|
||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
|
||||
const listEndIndex = Math.min(listStartIndex + visibleCount * 3, fullSize);
|
||||
i = listEndIndex - 1;
|
||||
list = fullList.slice(listStartIndex, listEndIndex);
|
||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
|
||||
}
|
||||
}
|
||||
} else if (starus === 1) {
|
||||
const gh = groupHeights.get(i);
|
||||
if (gh) {
|
||||
post += gh;
|
||||
i += groupItemCount - 1;
|
||||
} else {
|
||||
post += itemHeights.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 == 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(() => {
|
||||
if (listInited || container.state.list !== list) return;
|
||||
return {
|
||||
// 核心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 < listSize; i++) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight);
|
||||
}
|
||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
||||
for (let i = 0; i < size; i += groupItemCount) {
|
||||
let sum = 0;
|
||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
||||
for (let j = i; j < Math.min(i + groupItemCount, size); j++) {
|
||||
sum += itemHeights.get(j);
|
||||
}
|
||||
groupHeights.set(i, sum);
|
||||
}
|
||||
listInited = true;
|
||||
container.refresh();
|
||||
refreshCallback();
|
||||
},
|
||||
// 核心3:原汁原味的极速高度更新
|
||||
update: (absoluteIndex, node) => {
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node);
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0;
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop;
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap;
|
||||
const oldHeight = itemHeights.get(absoluteIndex);
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight);
|
||||
avg.add(newHeight);
|
||||
const offset = newHeight - (oldHeight || 0);
|
||||
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
|
||||
}
|
||||
},
|
||||
// 核心4:原汁原味的状态机滚动计算
|
||||
calc: (container, list) => {
|
||||
if (!listInited || !list) return null;
|
||||
const size = list.length;
|
||||
const visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32));
|
||||
let prev = padTop + topMargin + rowGap;
|
||||
let post = 0;
|
||||
let status = 0;
|
||||
let listStartIndex = 0;
|
||||
let listEndIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (status === 0) {
|
||||
const gh = groupHeights.get(i);
|
||||
if (gh && prev + gh < container.scrollTop) {
|
||||
prev += gh;
|
||||
i += Math.min(groupItemCount, size - i) - 1;
|
||||
} else {
|
||||
const ih = itemHeights.get(i);
|
||||
if (prev + ih < container.scrollTop) {
|
||||
prev += ih;
|
||||
} else {
|
||||
status = 1;
|
||||
let visibleStartIndex = Math.max(0, i);
|
||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
|
||||
listEndIndex = Math.min(listStartIndex + visibleCount * 3, size);
|
||||
i = listEndIndex - 1;
|
||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
|
||||
}
|
||||
}
|
||||
} else if (status === 1) {
|
||||
const gh = groupHeights.get(i);
|
||||
if (gh) {
|
||||
post += gh;
|
||||
i += groupItemCount - 1;
|
||||
} else {
|
||||
post += itemHeights.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
prevHeight: prev - padTop - topMargin - rowGap,
|
||||
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._listStartIndex = 0;
|
||||
container.state.renderedList = vs.reset(list, container) || [];
|
||||
requestAnimationFrame(() => {
|
||||
if (container.state.list !== list) return;
|
||||
vs.init(list, container.refresh);
|
||||
});
|
||||
});
|
||||
}, Util.makeDom(
|
||||
@ -707,19 +750,32 @@ if (typeof document !== "undefined") {
|
||||
});
|
||||
}
|
||||
Component.register("Resizer", (container) => {
|
||||
const isVertical = container.hasAttribute("vertical");
|
||||
container.isVertical = container.hasAttribute("vertical");
|
||||
const min = parseInt(container.getAttribute("min")) || 10;
|
||||
const max = parseInt(container.getAttribute("max")) || 1e3;
|
||||
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 newSize = startSize + (isVertical ? h : w);
|
||||
const newSize = startSize + (container.isVertical ? h : w);
|
||||
return newSize < min ? min : newSize > max ? max : newSize;
|
||||
};
|
||||
container.addEventListener("mousedown", (event) => {
|
||||
const startSize = isVertical ? target.offsetHeight : target.offsetWidth;
|
||||
const startSize = container.isVertical ? target.offsetHeight : target.offsetWidth;
|
||||
MouseMover.start(event, {
|
||||
onmousemove: ({ w, h }) => target.style[isVertical ? "height" : "width"] = getSize(startSize, w, h) + "px",
|
||||
onmouseup: ({ w, h }) => container.dispatchEvent(new CustomEvent("resize", { detail: { oldSize: startSize, newSize: getSize(startSize, w, h) }, bubbles: false }))
|
||||
onmousemove: ({ w, h }) => {
|
||||
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(
|
||||
@ -755,5 +811,6 @@ export {
|
||||
HTTP,
|
||||
MouseMover,
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@ -183,6 +183,20 @@
|
||||
"@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": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
@ -431,6 +445,20 @@
|
||||
"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",
|
||||
"lockfileHash": "4618e5dd",
|
||||
"browserHash": "7960c498",
|
||||
"lockfileHash": "99ec7e3e",
|
||||
"browserHash": "7e396f40",
|
||||
"optimized": {},
|
||||
"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",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@web/base",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@rollup/plugin-terser": "^1.0.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@web/base",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"type": "module",
|
||||
"main": "dist/base.js",
|
||||
"module": "dist/base.js",
|
||||
|
||||
63
src/form.js
63
src/form.js
@ -4,10 +4,23 @@ import { HTTP } from './http.js'
|
||||
Component.register('AutoForm', container => {
|
||||
if (!container.state.schema) container.state.schema = []
|
||||
container.vertical = container.hasAttribute('vertical')
|
||||
container.inline = container.hasAttribute('inline')
|
||||
container.request = { method: 'POST' }
|
||||
container.response = {}
|
||||
container.result = null
|
||||
container.data = NewState(container.data || {})
|
||||
|
||||
// 立即初始化数据代理,避免异步延迟导致 Select 等组件初始化时找不到值而重置
|
||||
if (!container.data || !container.data.__watch) {
|
||||
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.submit = (opt = {}) => {
|
||||
if (!container.form.reportValidity()) return globalThis.UI?.toast?.('{#verify failed#}', { type: 'danger' })
|
||||
@ -28,33 +41,42 @@ Component.register('AutoForm', container => {
|
||||
})
|
||||
}
|
||||
}, Util.makeDom(/*html*/`
|
||||
<div>
|
||||
<form $class="align-items-center \${this.vertical?'':'auto-grid-form'}" $onsubmit="event.stopPropagation();event.preventDefault();this.submit()">
|
||||
<div $class="'auto-form-root' + (this.inline ? ' auto-form-inline min-h-100 w-100' : '')">
|
||||
<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">
|
||||
<label $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||
<div control-wrapper class="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]">
|
||||
<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>
|
||||
<label $if="!this.inline" $name="item.name" class="col-form-label text-muted" $text="item.label"></label>
|
||||
<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]" $class="item.type === 'number' ? 'text-end' : ''">
|
||||
<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></option>
|
||||
<option $each="item.options" $value="item.value || item" $text="item.label || item"></option>
|
||||
</select>
|
||||
<div $if="['checkbox', 'radio'].includes(item.type)" >
|
||||
<label $each="item.options || [item.text||item.label||item.name]" as="option" $class="form-check\${item.vertical ? '' : ' form-check-inline'}">
|
||||
<input $name="item.name" class="form-check-input" $type="item.type" $.="item.setting" $value="item.options?option:'on'" $bind="this.data[item.name]">
|
||||
<span $text="option" class="form-check-label"></span>
|
||||
<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') + ' mb-0 d-flex align-items-center p-0'">
|
||||
<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 $if="!this.inline || (item.options && item.options.length > 0)" $text="option" class="form-check-label"></span>
|
||||
</label>
|
||||
</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>
|
||||
<textarea $if="item.type==='textarea'" $name="item.name" class="form-control" $.="item.setting" $bind="this.data[item.name]"></textarea>
|
||||
<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">
|
||||
<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 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>
|
||||
<button type="submit" class="btn btn-primary" $text="this.submitlabel || '{#Submit#}'"></button>
|
||||
</div>
|
||||
</form>
|
||||
</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 = []
|
||||
export const AutoForm = {
|
||||
@ -67,11 +89,8 @@ export const AutoForm = {
|
||||
_addAutoFormComponent: name => {
|
||||
const template = Component.getTemplate('AutoForm')
|
||||
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}>`))
|
||||
} 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)
|
||||
}
|
||||
$(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}>`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +99,7 @@ if (typeof document !== 'undefined') {
|
||||
_pendingAutoFormComponents.forEach(name => AutoForm._addAutoFormComponent(name))
|
||||
_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)
|
||||
}
|
||||
|
||||
|
||||
@ -28,19 +28,32 @@ if (typeof document !== 'undefined') {
|
||||
}
|
||||
|
||||
Component.register('Resizer', container => {
|
||||
const isVertical = container.hasAttribute('vertical')
|
||||
container.isVertical = container.hasAttribute('vertical')
|
||||
const min = parseInt(container.getAttribute('min')) || 10
|
||||
const max = parseInt(container.getAttribute('max')) || 1000
|
||||
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 newSize = startSize + (isVertical ? h : w)
|
||||
const newSize = startSize + (container.isVertical ? h : w)
|
||||
return newSize < min ? min : newSize > max ? max : newSize
|
||||
}
|
||||
container.addEventListener('mousedown', event => {
|
||||
const startSize = isVertical ? target.offsetHeight : target.offsetWidth
|
||||
const startSize = container.isVertical ? target.offsetHeight : target.offsetWidth
|
||||
MouseMover.start(event, {
|
||||
onmousemove: ({ w, h }) => target.style[isVertical ? 'height' : 'width'] = getSize(startSize, w, h) + 'px',
|
||||
onmouseup: ({ w, h }) => container.dispatchEvent(new CustomEvent('resize', { detail: { oldSize: startSize, newSize: getSize(startSize, w, h) }, bubbles: false })),
|
||||
onmousemove: ({ w, h }) => {
|
||||
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*/`
|
||||
|
||||
223
src/list.js
223
src/list.js
@ -1,117 +1,160 @@
|
||||
import { Component, NewState, Util, Hash } from '@web/state'
|
||||
|
||||
export const FastListComponent = Component.register('FastList', container => {
|
||||
export const VirtualScroll = () => {
|
||||
const itemHeights = new Map()
|
||||
const groupHeights = new Map()
|
||||
let groupItemCount = 1
|
||||
container.state.renderedList = []
|
||||
const avg = Util.newAvg()
|
||||
let containerPaddingTop = 0
|
||||
let containerRowGap = 0
|
||||
|
||||
let padTop = 0
|
||||
let rowGap = 0
|
||||
let topMargin = 0
|
||||
let itemMarginTop = null
|
||||
let itemMarginBottom = null
|
||||
let listStartIndex = 0
|
||||
let visibleStartIndex = 0
|
||||
let visibleCount = 10
|
||||
let listInited = false
|
||||
|
||||
container.onItemUpdate = (index, node) => {
|
||||
const absoluteIndex = index + listStartIndex
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node)
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop
|
||||
return {
|
||||
// 核心1:重置与首屏预渲染
|
||||
reset: (list, container) => {
|
||||
listInited = false
|
||||
itemHeights.clear()
|
||||
groupHeights.clear()
|
||||
avg.clear()
|
||||
topMargin = 0
|
||||
itemMarginTop = null
|
||||
itemMarginBottom = null
|
||||
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + containerRowGap
|
||||
const oldHeight = itemHeights.get(absoluteIndex)
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight)
|
||||
avg.add(newHeight)
|
||||
const offset = newHeight - (oldHeight || 0)
|
||||
const groupIndex = absoluteIndex - (absoluteIndex % groupItemCount)
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset)
|
||||
}
|
||||
}
|
||||
if (!list?.length) return []
|
||||
|
||||
container.refresh = () => {
|
||||
if (!listInited) return
|
||||
visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32))
|
||||
let list = []
|
||||
const fullList = container.state.list
|
||||
const fullSize = fullList.length
|
||||
let i = 0
|
||||
let prev = containerPaddingTop + topMargin + containerRowGap
|
||||
let post = 0
|
||||
let starus = 0
|
||||
for (i = 0; i < fullSize; i++) {
|
||||
if (starus === 0) {
|
||||
const gh = groupHeights.get(i)
|
||||
if (gh && prev + gh < container.scrollTop) {
|
||||
prev += gh
|
||||
i += Math.min(groupItemCount, fullSize - i) - 1
|
||||
} else {
|
||||
const ih = itemHeights.get(i)
|
||||
if (prev + ih < container.scrollTop) {
|
||||
prev += ih
|
||||
} else {
|
||||
starus = 1
|
||||
visibleStartIndex = Math.max(0, i)
|
||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount)
|
||||
const listEndIndex = Math.min(listStartIndex + visibleCount * 3, fullSize)
|
||||
i = listEndIndex - 1
|
||||
list = fullList.slice(listStartIndex, listEndIndex)
|
||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j)
|
||||
}
|
||||
}
|
||||
} else if (starus === 1) {
|
||||
const gh = groupHeights.get(i)
|
||||
if (gh) {
|
||||
post += gh
|
||||
i += groupItemCount - 1
|
||||
} else {
|
||||
post += itemHeights.get(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 size = list.length
|
||||
groupItemCount = Math.ceil(Math.sqrt(size)) || 10
|
||||
|
||||
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)) || []
|
||||
const style = window.getComputedStyle(container)
|
||||
padTop = parseFloat(style.paddingTop) || 0
|
||||
rowGap = parseFloat(style.rowGap) || 0
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (listInited || container.state.list !== list) return
|
||||
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 < listSize; i++) {
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight)
|
||||
}
|
||||
for (let i = 0; i < listSize; i += groupItemCount) {
|
||||
for (let i = 0; i < size; i += groupItemCount) {
|
||||
let sum = 0
|
||||
for (let j = i; j < Math.min(i + groupItemCount, listSize); j++) {
|
||||
for (let j = i; j < Math.min(i + groupItemCount, size); j++) {
|
||||
sum += itemHeights.get(j)
|
||||
}
|
||||
groupHeights.set(i, sum)
|
||||
}
|
||||
listInited = true
|
||||
container.refresh()
|
||||
refreshCallback()
|
||||
},
|
||||
|
||||
// 核心3:原汁原味的极速高度更新
|
||||
update: (absoluteIndex, node) => {
|
||||
if (itemMarginTop === null) {
|
||||
const style = window.getComputedStyle(node)
|
||||
itemMarginTop = parseFloat(style.marginTop) || 0
|
||||
itemMarginBottom = parseFloat(style.marginBottom) || 0
|
||||
}
|
||||
if (absoluteIndex === 0) topMargin = itemMarginTop
|
||||
|
||||
const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap
|
||||
const oldHeight = itemHeights.get(absoluteIndex)
|
||||
|
||||
if (newHeight !== oldHeight) {
|
||||
itemHeights.set(absoluteIndex, newHeight)
|
||||
avg.add(newHeight)
|
||||
const offset = newHeight - (oldHeight || 0)
|
||||
const groupIndex = absoluteIndex - (absoluteIndex % groupItemCount)
|
||||
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset)
|
||||
}
|
||||
},
|
||||
|
||||
// 核心4:原汁原味的状态机滚动计算
|
||||
calc: (container, list) => {
|
||||
if (!listInited || !list) return null
|
||||
|
||||
const size = list.length
|
||||
const visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32))
|
||||
|
||||
let prev = padTop + topMargin + rowGap
|
||||
let post = 0
|
||||
let status = 0
|
||||
let listStartIndex = 0
|
||||
let listEndIndex = 0
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (status === 0) {
|
||||
const gh = groupHeights.get(i)
|
||||
if (gh && prev + gh < container.scrollTop) {
|
||||
prev += gh
|
||||
i += Math.min(groupItemCount, size - i) - 1
|
||||
} else {
|
||||
const ih = itemHeights.get(i)
|
||||
if (prev + ih < container.scrollTop) {
|
||||
prev += ih
|
||||
} else {
|
||||
status = 1
|
||||
let visibleStartIndex = Math.max(0, i)
|
||||
listStartIndex = Math.max(0, visibleStartIndex - visibleCount)
|
||||
listEndIndex = Math.min(listStartIndex + visibleCount * 3, size)
|
||||
i = listEndIndex - 1
|
||||
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j)
|
||||
}
|
||||
}
|
||||
} else if (status === 1) {
|
||||
const gh = groupHeights.get(i)
|
||||
if (gh) {
|
||||
post += gh
|
||||
i += groupItemCount - 1
|
||||
} else {
|
||||
post += itemHeights.get(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
prevHeight: prev - padTop - topMargin - rowGap,
|
||||
postHeight: post,
|
||||
renderedList: list.slice(listStartIndex, listEndIndex),
|
||||
listStartIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(() => {
|
||||
if (container.state.list !== list) return
|
||||
vs.init(list, container.refresh)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ Component.register('Modal', container => {
|
||||
}, Util.makeDom(/*html*/`
|
||||
<div class="modal fade" data-bs-backdrop="static">
|
||||
<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">
|
||||
<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>
|
||||
@ -30,7 +30,7 @@ Component.register('Modal', container => {
|
||||
Component.register('Dialog', Component.getSetupFunction('Modal'), Util.makeDom(/*html*/`
|
||||
<div class="modal fade" data-bs-backdrop="static">
|
||||
<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 slot-id="body" class="modal-body"><div $html="this.state?.message"></div></div>
|
||||
<div class="modal-footer">
|
||||
@ -84,7 +84,7 @@ Component.register('Toast', container => {
|
||||
})
|
||||
}, Util.makeDom(/*html*/`
|
||||
<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="flex-grow-1">
|
||||
<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 = {}) {
|
||||
const delay = options.delay ?? 5000
|
||||
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)
|
||||
Promise.resolve().then(() => t.show())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user