@lingxia/elements 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component.d.ts +50 -0
- package/dist/component.js +139 -0
- package/dist/dom.d.ts +10 -0
- package/dist/dom.js +70 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/input.d.ts +117 -0
- package/dist/input.js +699 -0
- package/dist/native_component_wrapper_shared.d.ts +74 -0
- package/dist/native_component_wrapper_shared.js +193 -0
- package/dist/nativecomponent.d.ts +6 -0
- package/dist/nativecomponent.js +82 -0
- package/dist/navigator.d.ts +83 -0
- package/dist/navigator.js +388 -0
- package/dist/picker-web.d.ts +2 -0
- package/dist/picker-web.js +727 -0
- package/dist/picker.d.ts +77 -0
- package/dist/picker.js +342 -0
- package/dist/platform.d.ts +6 -0
- package/dist/platform.js +24 -0
- package/dist/text_component_native_attrs.d.ts +53 -0
- package/dist/text_component_native_attrs.js +111 -0
- package/dist/text_component_shared.d.ts +7 -0
- package/dist/text_component_shared.js +148 -0
- package/dist/textarea.d.ts +129 -0
- package/dist/textarea.js +841 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +1 -0
- package/dist/video.d.ts +134 -0
- package/dist/video.js +741 -0
- package/package.json +39 -0
- package/src/component.ts +164 -0
- package/src/dom.ts +83 -0
- package/src/index.ts +69 -0
- package/src/input.ts +799 -0
- package/src/native_component_wrapper_shared.ts +243 -0
- package/src/nativecomponent.ts +97 -0
- package/src/navigator.ts +533 -0
- package/src/picker-web.ts +719 -0
- package/src/picker.ts +418 -0
- package/src/platform.ts +25 -0
- package/src/text_component_native_attrs.ts +137 -0
- package/src/text_component_shared.ts +147 -0
- package/src/textarea.ts +959 -0
- package/src/types.ts +20 -0
- package/src/video.ts +855 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
const ITEM_H = 44;
|
|
2
|
+
const VISIBLE = 5;
|
|
3
|
+
const COL_H = ITEM_H * VISIBLE;
|
|
4
|
+
const PAD = Math.floor(VISIBLE / 2) * ITEM_H;
|
|
5
|
+
const _cn = typeof navigator !== 'undefined' && /^zh/i.test(navigator.language);
|
|
6
|
+
const FIRST_DOW = _cn ? 1 : 0; // 0=Sun 1=Mon
|
|
7
|
+
const L = _cn
|
|
8
|
+
? { cancel: '取消', confirm: '确定', last7: '近7天', last30: '近30天', thisWeek: '本周', lastWeek: '上周', thisMonth: '本月', lastMonth: '上月' }
|
|
9
|
+
: { cancel: 'Cancel', confirm: 'OK', last7: 'Last 7 days', last30: 'Last 30 days', thisWeek: 'This week', lastWeek: 'Last week', thisMonth: 'This month', lastMonth: 'Last month' };
|
|
10
|
+
let _css = false;
|
|
11
|
+
function ensureCSS() {
|
|
12
|
+
if (_css)
|
|
13
|
+
return;
|
|
14
|
+
_css = true;
|
|
15
|
+
const s = document.createElement('style');
|
|
16
|
+
s.textContent = `
|
|
17
|
+
.lxp-ov{position:fixed;inset:0;z-index:99999}
|
|
18
|
+
.lxp-sh{position:fixed;width:320px;background:#fff;border-radius:12px;box-shadow:0 4px 32px rgba(0,0,0,.12),0 0 0 .5px rgba(0,0,0,.06);opacity:0;transform:translateY(-6px);transition:opacity .2s ease,transform .2s ease;-webkit-user-select:none;user-select:none;max-height:70vh;overflow-y:auto;overflow-x:hidden}
|
|
19
|
+
.lxp-ov.lxp-in .lxp-sh{opacity:1;transform:translateY(0)}
|
|
20
|
+
.lxp-cols{display:flex;position:relative;height:${COL_H}px;margin:16px 0 0}
|
|
21
|
+
.lxp-col{flex:1;height:${COL_H}px;overflow-y:auto;padding:${PAD}px 0;overscroll-behavior:contain;scrollbar-width:none;-webkit-overflow-scrolling:touch}
|
|
22
|
+
.lxp-col::-webkit-scrollbar{display:none}
|
|
23
|
+
.lxp-item{height:${ITEM_H}px;display:flex;align-items:center;justify-content:center;font-size:18px;color:#333;cursor:default;white-space:nowrap;padding:0 8px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
24
|
+
.lxp-ln-t,.lxp-ln-b{position:absolute;left:20px;right:20px;height:.5px;background:rgba(0,0,0,.18);pointer-events:none;z-index:5}
|
|
25
|
+
.lxp-ln-t{top:${PAD}px}.lxp-ln-b{top:${PAD + ITEM_H}px}
|
|
26
|
+
.lxp-mk-t,.lxp-mk-b{position:absolute;left:0;right:0;pointer-events:none;z-index:4}
|
|
27
|
+
.lxp-mk-t{top:0;height:${PAD}px;background:linear-gradient(#fff 10%,rgba(255,255,255,.4))}
|
|
28
|
+
.lxp-mk-b{bottom:0;height:${PAD}px;background:linear-gradient(to top,#fff 10%,rgba(255,255,255,.4))}
|
|
29
|
+
.lxp-tsep{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:22px;font-weight:600;color:#8e8e93;pointer-events:none;z-index:5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
30
|
+
.lxp-btns{display:flex;gap:12px;padding:10px 16px 20px}
|
|
31
|
+
.lxp-btn{flex:1;height:46px;border:none;border-radius:12px;font-size:17px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;cursor:pointer;transition:opacity .15s;outline:none}
|
|
32
|
+
.lxp-btn:active{opacity:.7}
|
|
33
|
+
.lxp-btn:disabled{cursor:default}
|
|
34
|
+
.lxp-btn-c{background:#f2f2f7;color:#000;font-weight:500}
|
|
35
|
+
.lxp-btn-k{background:#007aff;color:#fff;font-weight:600}
|
|
36
|
+
.lxp-cal{padding:10px 8px 0}
|
|
37
|
+
.lxp-qk{display:flex;flex-direction:column;gap:6px;padding:0 4px 8px}
|
|
38
|
+
.lxp-qr{display:flex;gap:8px}
|
|
39
|
+
.lxp-qb{flex:1;height:28px;border:none;border-radius:14px;font-size:13px;font-weight:500;color:#007aff;background:rgba(0,122,255,.08);cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;outline:none}
|
|
40
|
+
.lxp-qb:active{opacity:.7}
|
|
41
|
+
.lxp-nav{display:flex;align-items:center;gap:4px;padding:0 4px;height:32px}
|
|
42
|
+
.lxp-nb{width:32px;height:32px;border:none;background:none;cursor:pointer;font-size:14px;color:#007aff;display:flex;align-items:center;justify-content:center;border-radius:6px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;outline:none}
|
|
43
|
+
.lxp-nb:disabled{color:#d1d1d6;cursor:default}
|
|
44
|
+
.lxp-nb:not(:disabled):hover{background:rgba(0,0,0,.04)}
|
|
45
|
+
.lxp-nt{flex:1;text-align:center;font-size:17px;font-weight:600;color:#000;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
46
|
+
.lxp-wk{display:grid;grid-template-columns:repeat(7,1fr);padding:12px 0 4px}
|
|
47
|
+
.lxp-wd{text-align:center;font-size:11px;font-weight:500;color:#8e8e93;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
48
|
+
.lxp-gr{display:grid;grid-template-columns:repeat(7,1fr)}
|
|
49
|
+
.lxp-dy{height:34px;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;font-size:15px;font-weight:500;color:#000;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:transparent}
|
|
50
|
+
.lxp-dy.ot{color:#c7c7cc}
|
|
51
|
+
.lxp-dy.dis{color:#d1d1d6;opacity:.3;cursor:default;pointer-events:none}
|
|
52
|
+
.lxp-dy.td::after{content:'';position:absolute;width:30px;height:30px;border-radius:50%;border:1.5px solid #007aff;pointer-events:none;box-sizing:border-box;top:50%;left:50%;transform:translate(-50%,-50%)}
|
|
53
|
+
.lxp-dy.sel{color:#fff;z-index:1}
|
|
54
|
+
.lxp-dy.sel::before{content:'';position:absolute;width:30px;height:30px;border-radius:50%;background:#007aff;z-index:-1;top:50%;left:50%;transform:translate(-50%,-50%)}
|
|
55
|
+
.lxp-dy.sel.td::after{display:none}
|
|
56
|
+
.lxp-dy.inr{background:rgba(0,122,255,.15)}
|
|
57
|
+
.lxp-dy.rs{background:linear-gradient(to right,transparent 50%,rgba(0,122,255,.15) 50%)}
|
|
58
|
+
.lxp-dy.re{background:linear-gradient(to left,transparent 50%,rgba(0,122,255,.15) 50%)}
|
|
59
|
+
.lxp-dy.rs.re{background:transparent}
|
|
60
|
+
`;
|
|
61
|
+
document.head.appendChild(s);
|
|
62
|
+
}
|
|
63
|
+
export function renderWebPicker(host, props, onChange) {
|
|
64
|
+
ensureCSS();
|
|
65
|
+
let alive = true;
|
|
66
|
+
let getValue = () => ({});
|
|
67
|
+
const mode = props.mode || 'selector';
|
|
68
|
+
const fields = props.fields || 'day';
|
|
69
|
+
const useCal = mode === 'date' && (fields === 'day' || fields === 'range');
|
|
70
|
+
const ov = mk('div', 'lxp-ov');
|
|
71
|
+
const sh = mk('div', 'lxp-sh');
|
|
72
|
+
ov.appendChild(sh);
|
|
73
|
+
function close() {
|
|
74
|
+
if (!alive)
|
|
75
|
+
return;
|
|
76
|
+
alive = false;
|
|
77
|
+
document.removeEventListener('keydown', onKey);
|
|
78
|
+
ov.classList.remove('lxp-in');
|
|
79
|
+
const done = () => { try {
|
|
80
|
+
ov.remove();
|
|
81
|
+
}
|
|
82
|
+
catch { } };
|
|
83
|
+
sh.addEventListener('transitionend', done, { once: true });
|
|
84
|
+
setTimeout(done, 250);
|
|
85
|
+
}
|
|
86
|
+
function cancel() { if (!alive)
|
|
87
|
+
return; onChange({ cancelled: true }); close(); }
|
|
88
|
+
function confirm() { if (!alive)
|
|
89
|
+
return; onChange({ ...getValue(), confirmed: true }); close(); }
|
|
90
|
+
const cancelBtn = mkBtn('c', props.cancelText || L.cancel, props.cancelButtonColor, props.cancelTextColor);
|
|
91
|
+
const confirmBtn = mkBtn('k', props.confirmText || L.confirm, props.confirmButtonColor, props.confirmTextColor);
|
|
92
|
+
cancelBtn.addEventListener('click', cancel);
|
|
93
|
+
confirmBtn.addEventListener('click', confirm);
|
|
94
|
+
const btns = mk('div', 'lxp-btns');
|
|
95
|
+
btns.appendChild(cancelBtn);
|
|
96
|
+
btns.appendChild(confirmBtn);
|
|
97
|
+
let initScrolls = null;
|
|
98
|
+
if (useCal) {
|
|
99
|
+
const cal = buildCalendar(fields, props, (val) => host.dispatchEvent(new CustomEvent('scroll', { detail: val, bubbles: true })), (ok) => { confirmBtn.disabled = !ok; confirmBtn.style.opacity = ok ? '1' : '0.45'; });
|
|
100
|
+
sh.appendChild(cal.el);
|
|
101
|
+
getValue = cal.getValue;
|
|
102
|
+
if (fields === 'range' && !props.value) {
|
|
103
|
+
confirmBtn.disabled = true;
|
|
104
|
+
confirmBtn.style.opacity = '0.45';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const content = buildContent(mode, props);
|
|
109
|
+
const colsEl = buildWheelColumns(content, () => host.dispatchEvent(new CustomEvent('scroll', { detail: content.value(), bubbles: true })));
|
|
110
|
+
sh.appendChild(colsEl);
|
|
111
|
+
getValue = content.value;
|
|
112
|
+
initScrolls = () => content.cols.forEach(c => { if (c.el)
|
|
113
|
+
c.el.scrollTop = (c.idx || 0) * ITEM_H; });
|
|
114
|
+
}
|
|
115
|
+
sh.appendChild(btns);
|
|
116
|
+
document.body.appendChild(ov);
|
|
117
|
+
void ov.offsetHeight;
|
|
118
|
+
if (initScrolls)
|
|
119
|
+
initScrolls();
|
|
120
|
+
positionPopover(sh, host);
|
|
121
|
+
requestAnimationFrame(() => ov.classList.add('lxp-in'));
|
|
122
|
+
ov.addEventListener('mousedown', e => { if (e.target === ov)
|
|
123
|
+
cancel(); });
|
|
124
|
+
const onKey = (e) => {
|
|
125
|
+
if (e.key === 'Escape')
|
|
126
|
+
cancel();
|
|
127
|
+
if (e.key === 'Enter')
|
|
128
|
+
confirm();
|
|
129
|
+
};
|
|
130
|
+
document.addEventListener('keydown', onKey);
|
|
131
|
+
return () => {
|
|
132
|
+
if (alive) {
|
|
133
|
+
alive = false;
|
|
134
|
+
document.removeEventListener('keydown', onKey);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
ov.remove();
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function mkBtn(type, text, bgColor, textColor) {
|
|
143
|
+
const b = document.createElement('button');
|
|
144
|
+
b.className = `lxp-btn lxp-btn-${type}`;
|
|
145
|
+
b.textContent = text;
|
|
146
|
+
if (bgColor)
|
|
147
|
+
b.style.background = bgColor;
|
|
148
|
+
if (textColor)
|
|
149
|
+
b.style.color = textColor;
|
|
150
|
+
return b;
|
|
151
|
+
}
|
|
152
|
+
function getAnchorRect(host) {
|
|
153
|
+
const hr = host.getBoundingClientRect();
|
|
154
|
+
if (hr.width > 0 && hr.height > 0)
|
|
155
|
+
return hr;
|
|
156
|
+
const prev = host.previousElementSibling;
|
|
157
|
+
if (prev)
|
|
158
|
+
return prev.getBoundingClientRect();
|
|
159
|
+
if (host.parentElement)
|
|
160
|
+
return host.parentElement.getBoundingClientRect();
|
|
161
|
+
return new DOMRect(window.innerWidth / 2 - 160, window.innerHeight / 3, 0, 0);
|
|
162
|
+
}
|
|
163
|
+
function positionPopover(sh, host) {
|
|
164
|
+
const W = 320;
|
|
165
|
+
const GAP = 8;
|
|
166
|
+
const vw = window.innerWidth;
|
|
167
|
+
const vh = window.innerHeight;
|
|
168
|
+
const anchor = getAnchorRect(host);
|
|
169
|
+
let left = anchor.left + anchor.width / 2 - W / 2;
|
|
170
|
+
if (left + W > vw - GAP)
|
|
171
|
+
left = vw - GAP - W;
|
|
172
|
+
if (left < GAP)
|
|
173
|
+
left = GAP;
|
|
174
|
+
const shH = sh.offsetHeight;
|
|
175
|
+
const spaceBelow = vh - anchor.bottom - GAP;
|
|
176
|
+
const spaceAbove = anchor.top - GAP;
|
|
177
|
+
let top;
|
|
178
|
+
if (shH <= spaceBelow || spaceBelow >= spaceAbove) {
|
|
179
|
+
top = anchor.bottom + GAP;
|
|
180
|
+
sh.style.transformOrigin = 'top left';
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
top = anchor.top - GAP - shH;
|
|
184
|
+
sh.style.transformOrigin = 'bottom left';
|
|
185
|
+
}
|
|
186
|
+
sh.style.left = left + 'px';
|
|
187
|
+
sh.style.top = top + 'px';
|
|
188
|
+
}
|
|
189
|
+
function buildWheelColumns(content, onScrollFn) {
|
|
190
|
+
const wrap = mk('div', 'lxp-cols');
|
|
191
|
+
wrap.appendChild(mk('div', 'lxp-ln-t'));
|
|
192
|
+
wrap.appendChild(mk('div', 'lxp-ln-b'));
|
|
193
|
+
wrap.appendChild(mk('div', 'lxp-mk-t'));
|
|
194
|
+
wrap.appendChild(mk('div', 'lxp-mk-b'));
|
|
195
|
+
if (content.timeSep) {
|
|
196
|
+
const sep = mk('div', 'lxp-tsep');
|
|
197
|
+
sep.textContent = ':';
|
|
198
|
+
wrap.appendChild(sep);
|
|
199
|
+
}
|
|
200
|
+
content.cols.forEach((c, ci) => {
|
|
201
|
+
const colEl = makeColumn(c.items);
|
|
202
|
+
wrap.appendChild(colEl);
|
|
203
|
+
c.el = colEl;
|
|
204
|
+
const cb = () => { var _a; (_a = content.onScroll) === null || _a === void 0 ? void 0 : _a.call(content, ci, idxOf(colEl)); onScrollFn(); };
|
|
205
|
+
attachWheel(colEl, cb);
|
|
206
|
+
attachSnap(colEl, cb);
|
|
207
|
+
attachLiveScroll(colEl, cb);
|
|
208
|
+
});
|
|
209
|
+
return wrap;
|
|
210
|
+
}
|
|
211
|
+
function buildCalendar(fields, props, onScroll, onComplete) {
|
|
212
|
+
const isRange = fields === 'range';
|
|
213
|
+
const minDT = parseDT(props.start);
|
|
214
|
+
const maxDT = parseDT(props.end);
|
|
215
|
+
const now = new Date();
|
|
216
|
+
let curY = now.getFullYear(), curM = now.getMonth() + 1;
|
|
217
|
+
let selDate = null;
|
|
218
|
+
let rStart = null;
|
|
219
|
+
let rEnd = null;
|
|
220
|
+
let pickingStart = true;
|
|
221
|
+
let tempStart = null;
|
|
222
|
+
let complete = !isRange;
|
|
223
|
+
if (isRange) {
|
|
224
|
+
const vals = Array.isArray(props.value) ? props.value : [];
|
|
225
|
+
if (vals[0]) {
|
|
226
|
+
rStart = parseDT(vals[0]);
|
|
227
|
+
if (rStart) {
|
|
228
|
+
curY = rStart.y;
|
|
229
|
+
curM = rStart.m;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (vals[1])
|
|
233
|
+
rEnd = parseDT(vals[1]);
|
|
234
|
+
complete = !!(rStart && rEnd);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const v = parseDT(props.value);
|
|
238
|
+
if (v) {
|
|
239
|
+
selDate = v;
|
|
240
|
+
curY = v.y;
|
|
241
|
+
curM = v.m;
|
|
242
|
+
}
|
|
243
|
+
else
|
|
244
|
+
selDate = { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };
|
|
245
|
+
}
|
|
246
|
+
const el = mk('div', 'lxp-cal');
|
|
247
|
+
if (isRange) {
|
|
248
|
+
const qk = mk('div', 'lxp-qk');
|
|
249
|
+
const row1 = [[L.last7, 'l7'], [L.last30, 'l30'], [L.thisWeek, 'tw']];
|
|
250
|
+
const row2 = [[L.lastWeek, 'lw'], [L.thisMonth, 'tm'], [L.lastMonth, 'lm']];
|
|
251
|
+
[row1, row2].forEach(row => {
|
|
252
|
+
const r = mk('div', 'lxp-qr');
|
|
253
|
+
row.forEach(([label, key]) => {
|
|
254
|
+
const b = document.createElement('button');
|
|
255
|
+
b.className = 'lxp-qb';
|
|
256
|
+
b.textContent = label;
|
|
257
|
+
b.addEventListener('click', () => applyQuick(key));
|
|
258
|
+
r.appendChild(b);
|
|
259
|
+
});
|
|
260
|
+
qk.appendChild(r);
|
|
261
|
+
});
|
|
262
|
+
el.appendChild(qk);
|
|
263
|
+
}
|
|
264
|
+
const nav = mk('div', 'lxp-nav');
|
|
265
|
+
const btnPY = mkNavBtn('\u00AB');
|
|
266
|
+
const btnPM = mkNavBtn('\u2039');
|
|
267
|
+
const titleEl = mk('span', 'lxp-nt');
|
|
268
|
+
const btnNM = mkNavBtn('\u203A');
|
|
269
|
+
const btnNY = mkNavBtn('\u00BB');
|
|
270
|
+
btnPY.addEventListener('click', () => changeMonth(-12));
|
|
271
|
+
btnPM.addEventListener('click', () => changeMonth(-1));
|
|
272
|
+
btnNM.addEventListener('click', () => changeMonth(1));
|
|
273
|
+
btnNY.addEventListener('click', () => changeMonth(12));
|
|
274
|
+
[btnPY, btnPM, titleEl, btnNM, btnNY].forEach(n => nav.appendChild(n));
|
|
275
|
+
el.appendChild(nav);
|
|
276
|
+
const wk = mk('div', 'lxp-wk');
|
|
277
|
+
weekdayLabels().forEach(d => { const s = mk('span', 'lxp-wd'); s.textContent = d; wk.appendChild(s); });
|
|
278
|
+
el.appendChild(wk);
|
|
279
|
+
const gridEl = mk('div', 'lxp-gr');
|
|
280
|
+
const cells = [];
|
|
281
|
+
let currentDays = [];
|
|
282
|
+
for (let i = 0; i < 42; i++) {
|
|
283
|
+
const cell = document.createElement('div');
|
|
284
|
+
cell.className = 'lxp-dy';
|
|
285
|
+
cell.addEventListener('click', () => {
|
|
286
|
+
const day = currentDays[i];
|
|
287
|
+
if (day)
|
|
288
|
+
selectDay(day);
|
|
289
|
+
});
|
|
290
|
+
cells.push(cell);
|
|
291
|
+
gridEl.appendChild(cell);
|
|
292
|
+
}
|
|
293
|
+
el.appendChild(gridEl);
|
|
294
|
+
render();
|
|
295
|
+
onComplete(complete);
|
|
296
|
+
function changeMonth(offset) {
|
|
297
|
+
const d = new Date(curY, curM - 1 + offset, 1);
|
|
298
|
+
curY = d.getFullYear();
|
|
299
|
+
curM = d.getMonth() + 1;
|
|
300
|
+
render();
|
|
301
|
+
}
|
|
302
|
+
function applyQuick(key) {
|
|
303
|
+
const td = todayDT();
|
|
304
|
+
let s, e;
|
|
305
|
+
switch (key) {
|
|
306
|
+
case 'l7':
|
|
307
|
+
s = addDays(td, -6);
|
|
308
|
+
e = td;
|
|
309
|
+
break;
|
|
310
|
+
case 'l30':
|
|
311
|
+
s = addDays(td, -29);
|
|
312
|
+
e = td;
|
|
313
|
+
break;
|
|
314
|
+
case 'tw':
|
|
315
|
+
s = weekStart(td);
|
|
316
|
+
e = td;
|
|
317
|
+
break;
|
|
318
|
+
case 'lw': {
|
|
319
|
+
const ws = weekStart(td);
|
|
320
|
+
s = addDays(ws, -7);
|
|
321
|
+
e = addDays(s, 6);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case 'tm':
|
|
325
|
+
s = { y: td.y, m: td.m, d: 1 };
|
|
326
|
+
e = td;
|
|
327
|
+
break;
|
|
328
|
+
case 'lm': {
|
|
329
|
+
const som = { y: td.y, m: td.m, d: 1 };
|
|
330
|
+
e = addDays(som, -1);
|
|
331
|
+
s = { y: e.y, m: e.m, d: 1 };
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
default: return;
|
|
335
|
+
}
|
|
336
|
+
rStart = s;
|
|
337
|
+
rEnd = e;
|
|
338
|
+
pickingStart = true;
|
|
339
|
+
tempStart = null;
|
|
340
|
+
complete = true;
|
|
341
|
+
onComplete(true);
|
|
342
|
+
curY = s.y;
|
|
343
|
+
curM = s.m;
|
|
344
|
+
render();
|
|
345
|
+
emitScroll();
|
|
346
|
+
}
|
|
347
|
+
function selectDay(day) {
|
|
348
|
+
if (minDT && dtKey(day) < dtKey(minDT))
|
|
349
|
+
return;
|
|
350
|
+
if (maxDT && dtKey(day) > dtKey(maxDT))
|
|
351
|
+
return;
|
|
352
|
+
if (isRange) {
|
|
353
|
+
if (pickingStart) {
|
|
354
|
+
tempStart = day;
|
|
355
|
+
rStart = day;
|
|
356
|
+
rEnd = day;
|
|
357
|
+
pickingStart = false;
|
|
358
|
+
complete = false;
|
|
359
|
+
onComplete(false);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const s = tempStart;
|
|
363
|
+
if (dtKey(day) < dtKey(s)) {
|
|
364
|
+
rStart = day;
|
|
365
|
+
rEnd = s;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
rStart = s;
|
|
369
|
+
rEnd = day;
|
|
370
|
+
}
|
|
371
|
+
pickingStart = true;
|
|
372
|
+
tempStart = null;
|
|
373
|
+
complete = true;
|
|
374
|
+
onComplete(true);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
selDate = day;
|
|
379
|
+
}
|
|
380
|
+
if (day.m !== curM || day.y !== curY) {
|
|
381
|
+
curY = day.y;
|
|
382
|
+
curM = day.m;
|
|
383
|
+
}
|
|
384
|
+
render();
|
|
385
|
+
emitScroll();
|
|
386
|
+
}
|
|
387
|
+
function emitScroll() {
|
|
388
|
+
onScroll(getVal());
|
|
389
|
+
}
|
|
390
|
+
function getVal() {
|
|
391
|
+
if (isRange && rStart && rEnd)
|
|
392
|
+
return { value: [fmtDT(rStart), fmtDT(rEnd)] };
|
|
393
|
+
if (selDate)
|
|
394
|
+
return { value: fmtDT(selDate) };
|
|
395
|
+
return {};
|
|
396
|
+
}
|
|
397
|
+
function render() {
|
|
398
|
+
titleEl.textContent = new Date(curY, curM - 1).toLocaleDateString(navigator.language, { year: 'numeric', month: 'long' });
|
|
399
|
+
updateNavBtns();
|
|
400
|
+
currentDays = calGrid(curY, curM);
|
|
401
|
+
const td = todayDT();
|
|
402
|
+
const sameDay = rStart && rEnd ? dtEq(rStart, rEnd) : false;
|
|
403
|
+
for (let i = 0; i < 42; i++) {
|
|
404
|
+
const cell = cells[i];
|
|
405
|
+
const day = currentDays[i];
|
|
406
|
+
cell.textContent = String(day.d);
|
|
407
|
+
const inMonth = day.m === curM && day.y === curY;
|
|
408
|
+
const disabled = (minDT && dtKey(day) < dtKey(minDT)) || (maxDT && dtKey(day) > dtKey(maxDT));
|
|
409
|
+
const isToday = dtEq(day, td);
|
|
410
|
+
const isStart = rStart ? dtEq(day, rStart) : false;
|
|
411
|
+
const isEnd = rEnd ? dtEq(day, rEnd) : false;
|
|
412
|
+
const isSel = isRange ? (isStart || isEnd) : (selDate ? dtEq(day, selDate) : false);
|
|
413
|
+
const inRange = rStart && rEnd && !sameDay && dtKey(day) >= dtKey(rStart) && dtKey(day) <= dtKey(rEnd);
|
|
414
|
+
let cn = 'lxp-dy';
|
|
415
|
+
if (!inMonth)
|
|
416
|
+
cn += ' ot';
|
|
417
|
+
if (disabled)
|
|
418
|
+
cn += ' dis';
|
|
419
|
+
if (isToday && !isSel)
|
|
420
|
+
cn += ' td';
|
|
421
|
+
if (isSel)
|
|
422
|
+
cn += ' sel';
|
|
423
|
+
if (isStart && inRange)
|
|
424
|
+
cn += ' rs';
|
|
425
|
+
if (isEnd && inRange)
|
|
426
|
+
cn += ' re';
|
|
427
|
+
if (inRange && !isStart && !isEnd)
|
|
428
|
+
cn += ' inr';
|
|
429
|
+
cell.className = cn;
|
|
430
|
+
cell.style.pointerEvents = disabled ? 'none' : '';
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function updateNavBtns() {
|
|
434
|
+
if (minDT) {
|
|
435
|
+
const prevM = prevMonth(curY, curM);
|
|
436
|
+
const prevY = { y: curY - 1, m: curM };
|
|
437
|
+
btnPM.disabled = dtKey({ ...prevM, d: daysIn(prevM.y, prevM.m) }) < dtKey(minDT);
|
|
438
|
+
btnPY.disabled = dtKey({ ...prevY, d: daysIn(prevY.y, prevY.m) }) < dtKey(minDT);
|
|
439
|
+
}
|
|
440
|
+
if (maxDT) {
|
|
441
|
+
const nextM = nextMonth(curY, curM);
|
|
442
|
+
const nextY = { y: curY + 1, m: curM };
|
|
443
|
+
btnNM.disabled = dtKey({ ...nextM, d: 1 }) > dtKey(maxDT);
|
|
444
|
+
btnNY.disabled = dtKey({ ...nextY, d: 1 }) > dtKey(maxDT);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { el, getValue: getVal };
|
|
448
|
+
}
|
|
449
|
+
function mkNavBtn(text) {
|
|
450
|
+
const b = document.createElement('button');
|
|
451
|
+
b.className = 'lxp-nb';
|
|
452
|
+
b.textContent = text;
|
|
453
|
+
return b;
|
|
454
|
+
}
|
|
455
|
+
function buildContent(mode, p) {
|
|
456
|
+
switch (mode) {
|
|
457
|
+
case 'date': {
|
|
458
|
+
const f = p.fields || 'day';
|
|
459
|
+
if (f === 'year')
|
|
460
|
+
return yearPicker(p);
|
|
461
|
+
if (f === 'month')
|
|
462
|
+
return monthPicker(p);
|
|
463
|
+
return yearPicker(p);
|
|
464
|
+
}
|
|
465
|
+
case 'time': return timePicker(p);
|
|
466
|
+
case 'cascading': return cascadePicker(p);
|
|
467
|
+
case 'multiSelector': return multiPicker(p);
|
|
468
|
+
default: return selectorPicker(p);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function selectorPicker(p) {
|
|
472
|
+
var _a;
|
|
473
|
+
const items = ((_a = p.columns) === null || _a === void 0 ? void 0 : _a[0]) || [];
|
|
474
|
+
const idx = typeof p.defaultIndex === 'number' ? p.defaultIndex : 0;
|
|
475
|
+
const cols = [{ items, idx }];
|
|
476
|
+
return {
|
|
477
|
+
cols,
|
|
478
|
+
value: () => { const i = idxOf(cols[0].el); return { index: i, value: items[i] }; },
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function multiPicker(p) {
|
|
482
|
+
const all = p.columns || [];
|
|
483
|
+
const defs = Array.isArray(p.defaultIndex) ? p.defaultIndex : [];
|
|
484
|
+
const cols = all.map((items, i) => ({ items, idx: defs[i] || 0 }));
|
|
485
|
+
return {
|
|
486
|
+
cols,
|
|
487
|
+
value: () => {
|
|
488
|
+
const indices = cols.map(c => idxOf(c.el));
|
|
489
|
+
const values = cols.map((c, i) => c.items[indices[i]]);
|
|
490
|
+
return { index: indices, value: values };
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function cascadePicker(p) {
|
|
495
|
+
const data = p.columns;
|
|
496
|
+
if (!data || data.length < 2)
|
|
497
|
+
return { cols: [{ items: [], idx: 0 }], value: () => ({}) };
|
|
498
|
+
const [first, map] = data;
|
|
499
|
+
const defs = Array.isArray(p.defaultIndex) ? p.defaultIndex : [0, 0];
|
|
500
|
+
const cols = [
|
|
501
|
+
{ items: first, idx: defs[0] || 0 },
|
|
502
|
+
{ items: map[first[defs[0] || 0]] || [], idx: defs[1] || 0 },
|
|
503
|
+
];
|
|
504
|
+
return {
|
|
505
|
+
cols,
|
|
506
|
+
value: () => {
|
|
507
|
+
const i0 = idxOf(cols[0].el), i1 = idxOf(cols[1].el);
|
|
508
|
+
return { index: [i0, i1], value: [first[i0], (map[first[i0]] || [])[i1]] };
|
|
509
|
+
},
|
|
510
|
+
onScroll: (ci, si) => { if (ci === 0)
|
|
511
|
+
replaceItems(cols[1].el, map[first[si]] || []); },
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function yearPicker(p) {
|
|
515
|
+
const now = new Date();
|
|
516
|
+
let y = now.getFullYear();
|
|
517
|
+
if (p.value) {
|
|
518
|
+
const v = parseInt(String(p.value));
|
|
519
|
+
if (!isNaN(v))
|
|
520
|
+
y = v;
|
|
521
|
+
}
|
|
522
|
+
const s = p.start ? parseInt(String(p.start).slice(0, 4)) : now.getFullYear() - 10;
|
|
523
|
+
const e = p.end ? parseInt(String(p.end).slice(0, 4)) : now.getFullYear() + 10;
|
|
524
|
+
const years = seq(s, e).map(v => `${v}`);
|
|
525
|
+
const cols = [{ items: years, idx: clamp(y - s, 0, years.length - 1) }];
|
|
526
|
+
return {
|
|
527
|
+
cols,
|
|
528
|
+
value: () => { const vy = s + idxOf(cols[0].el); return { value: `${vy}` }; },
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function monthPicker(p) {
|
|
532
|
+
const now = new Date();
|
|
533
|
+
let y = now.getFullYear(), m = now.getMonth() + 1;
|
|
534
|
+
if (p.value) {
|
|
535
|
+
const ps = String(p.value).split('-').map(Number);
|
|
536
|
+
if (ps[0])
|
|
537
|
+
y = ps[0];
|
|
538
|
+
if (ps[1])
|
|
539
|
+
m = ps[1];
|
|
540
|
+
}
|
|
541
|
+
const sY = p.start ? parseInt(String(p.start).slice(0, 4)) : now.getFullYear() - 10;
|
|
542
|
+
const eY = p.end ? parseInt(String(p.end).slice(0, 4)) : now.getFullYear() + 10;
|
|
543
|
+
const years = seq(sY, eY).map(v => `${v}`);
|
|
544
|
+
const months = seq(1, 12).map(v => _cn ? `${v}月` : String(v).padStart(2, '0'));
|
|
545
|
+
const cols = [
|
|
546
|
+
{ items: years, idx: clamp(y - sY, 0, years.length - 1) },
|
|
547
|
+
{ items: months, idx: clamp(m - 1, 0, 11) },
|
|
548
|
+
];
|
|
549
|
+
return {
|
|
550
|
+
cols,
|
|
551
|
+
value: () => {
|
|
552
|
+
const vy = sY + idxOf(cols[0].el);
|
|
553
|
+
const vm = idxOf(cols[1].el) + 1;
|
|
554
|
+
return { value: `${vy}-${z(vm)}` };
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function timePicker(p) {
|
|
559
|
+
var _a, _b, _c;
|
|
560
|
+
const startMin = (_a = parseTime(p.start)) !== null && _a !== void 0 ? _a : 0;
|
|
561
|
+
const endMin = (_b = parseTime(p.end)) !== null && _b !== void 0 ? _b : 23 * 60 + 59;
|
|
562
|
+
const startH = Math.floor(startMin / 60), endH = Math.floor(endMin / 60);
|
|
563
|
+
const sM = startMin % 60, eM = endMin % 60;
|
|
564
|
+
function minsFor(h) {
|
|
565
|
+
if (startH === endH)
|
|
566
|
+
return seq(sM, eM);
|
|
567
|
+
if (h === startH)
|
|
568
|
+
return seq(sM, 59);
|
|
569
|
+
if (h === endH)
|
|
570
|
+
return seq(0, eM);
|
|
571
|
+
return seq(0, 59);
|
|
572
|
+
}
|
|
573
|
+
let curH = startH, curMinute = 0;
|
|
574
|
+
if (p.value) {
|
|
575
|
+
const init = (_c = parseTime(p.value)) !== null && _c !== void 0 ? _c : startMin;
|
|
576
|
+
const clamped = clamp(init, startMin, endMin);
|
|
577
|
+
curH = Math.floor(clamped / 60);
|
|
578
|
+
curMinute = clamped % 60;
|
|
579
|
+
}
|
|
580
|
+
const hours = seq(startH, endH);
|
|
581
|
+
const initMins = minsFor(curH);
|
|
582
|
+
const cols = [
|
|
583
|
+
{ items: hours.map(v => z(v)), idx: curH - startH },
|
|
584
|
+
{ items: initMins.map(v => z(v)), idx: Math.max(0, initMins.indexOf(curMinute)) },
|
|
585
|
+
];
|
|
586
|
+
return {
|
|
587
|
+
cols,
|
|
588
|
+
timeSep: true,
|
|
589
|
+
value: () => {
|
|
590
|
+
const vh = startH + idxOf(cols[0].el);
|
|
591
|
+
const ma = minsFor(vh);
|
|
592
|
+
const mi = clamp(idxOf(cols[1].el), 0, ma.length - 1);
|
|
593
|
+
return { value: `${z(vh)}:${z(ma[mi])}` };
|
|
594
|
+
},
|
|
595
|
+
onScroll: (ci) => {
|
|
596
|
+
if (ci === 0) {
|
|
597
|
+
const vh = startH + idxOf(cols[0].el);
|
|
598
|
+
const newMins = minsFor(vh);
|
|
599
|
+
replaceItems(cols[1].el, newMins.map(v => z(v)));
|
|
600
|
+
cols[1].items = newMins.map(v => z(v));
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function mk(tag, cls) { const e = document.createElement(tag); e.className = cls; return e; }
|
|
606
|
+
function makeColumn(items) {
|
|
607
|
+
const col = document.createElement('div');
|
|
608
|
+
col.className = 'lxp-col';
|
|
609
|
+
items.forEach((text, i) => {
|
|
610
|
+
const d = mk('div', 'lxp-item');
|
|
611
|
+
d.textContent = text;
|
|
612
|
+
d.addEventListener('click', () => col.scrollTo({ top: i * ITEM_H, behavior: 'smooth' }));
|
|
613
|
+
col.appendChild(d);
|
|
614
|
+
});
|
|
615
|
+
return col;
|
|
616
|
+
}
|
|
617
|
+
function idxOf(col) { return Math.max(0, Math.round(col.scrollTop / ITEM_H)); }
|
|
618
|
+
function attachWheel(col, onMove) {
|
|
619
|
+
let acc = 0;
|
|
620
|
+
col.addEventListener('wheel', e => {
|
|
621
|
+
e.preventDefault();
|
|
622
|
+
acc += e.deltaY;
|
|
623
|
+
const threshold = Math.abs(e.deltaY) > 20 ? 1 : ITEM_H / 2;
|
|
624
|
+
if (Math.abs(acc) >= threshold) {
|
|
625
|
+
const dir = Math.sign(acc);
|
|
626
|
+
acc = 0;
|
|
627
|
+
const maxIdx = Math.max(0, Math.round((col.scrollHeight - col.clientHeight) / ITEM_H));
|
|
628
|
+
const cur = idxOf(col);
|
|
629
|
+
const next = clamp(cur + dir, 0, maxIdx);
|
|
630
|
+
if (next !== cur) {
|
|
631
|
+
col.scrollTo({ top: next * ITEM_H, behavior: 'smooth' });
|
|
632
|
+
onMove === null || onMove === void 0 ? void 0 : onMove();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}, { passive: false });
|
|
636
|
+
}
|
|
637
|
+
function attachSnap(col, onSettled) {
|
|
638
|
+
let t;
|
|
639
|
+
let snapping = false;
|
|
640
|
+
col.addEventListener('scroll', () => {
|
|
641
|
+
if (snapping)
|
|
642
|
+
return;
|
|
643
|
+
clearTimeout(t);
|
|
644
|
+
t = window.setTimeout(() => {
|
|
645
|
+
const idx = idxOf(col);
|
|
646
|
+
const target = idx * ITEM_H;
|
|
647
|
+
if (Math.abs(col.scrollTop - target) > 1) {
|
|
648
|
+
snapping = true;
|
|
649
|
+
col.scrollTo({ top: target, behavior: 'smooth' });
|
|
650
|
+
setTimeout(() => { snapping = false; }, 300);
|
|
651
|
+
}
|
|
652
|
+
onSettled === null || onSettled === void 0 ? void 0 : onSettled();
|
|
653
|
+
}, 120);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function attachLiveScroll(col, onMove) {
|
|
657
|
+
let last = -1;
|
|
658
|
+
col.addEventListener('scroll', () => {
|
|
659
|
+
const cur = idxOf(col);
|
|
660
|
+
if (cur !== last) {
|
|
661
|
+
last = cur;
|
|
662
|
+
onMove === null || onMove === void 0 ? void 0 : onMove();
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function replaceItems(col, items) {
|
|
667
|
+
const frag = document.createDocumentFragment();
|
|
668
|
+
items.forEach((text, i) => {
|
|
669
|
+
const d = mk('div', 'lxp-item');
|
|
670
|
+
d.textContent = text;
|
|
671
|
+
d.addEventListener('click', () => col.scrollTo({ top: i * ITEM_H, behavior: 'smooth' }));
|
|
672
|
+
frag.appendChild(d);
|
|
673
|
+
});
|
|
674
|
+
col.innerHTML = '';
|
|
675
|
+
col.appendChild(frag);
|
|
676
|
+
col.scrollTop = 0;
|
|
677
|
+
}
|
|
678
|
+
function dtKey(d) { return d.y * 10000 + d.m * 100 + d.d; }
|
|
679
|
+
function dtEq(a, b) { return dtKey(a) === dtKey(b); }
|
|
680
|
+
function todayDT() { const n = new Date(); return { y: n.getFullYear(), m: n.getMonth() + 1, d: n.getDate() }; }
|
|
681
|
+
function addDays(d, days) {
|
|
682
|
+
const date = new Date(d.y, d.m - 1, d.d + days);
|
|
683
|
+
return { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate() };
|
|
684
|
+
}
|
|
685
|
+
function weekStart(d) {
|
|
686
|
+
const date = new Date(d.y, d.m - 1, d.d);
|
|
687
|
+
const diff = (date.getDay() - FIRST_DOW + 7) % 7;
|
|
688
|
+
return addDays(d, -diff);
|
|
689
|
+
}
|
|
690
|
+
function prevMonth(y, m) { const d = new Date(y, m - 2, 1); return { y: d.getFullYear(), m: d.getMonth() + 1 }; }
|
|
691
|
+
function nextMonth(y, m) { const d = new Date(y, m, 1); return { y: d.getFullYear(), m: d.getMonth() + 1 }; }
|
|
692
|
+
function calGrid(y, m) {
|
|
693
|
+
const first = new Date(y, m - 1, 1);
|
|
694
|
+
const offset = (first.getDay() - FIRST_DOW + 7) % 7;
|
|
695
|
+
return Array.from({ length: 42 }, (_, i) => {
|
|
696
|
+
const d = new Date(y, m - 1, 1 - offset + i);
|
|
697
|
+
return { y: d.getFullYear(), m: d.getMonth() + 1, d: d.getDate() };
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function weekdayLabels() {
|
|
701
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
702
|
+
const d = new Date(2025, 0, 5 + ((i + FIRST_DOW) % 7));
|
|
703
|
+
return d.toLocaleDateString(navigator.language, { weekday: 'short' });
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
function parseDT(s) {
|
|
707
|
+
if (!s || typeof s !== 'string')
|
|
708
|
+
return null;
|
|
709
|
+
const p = s.split('-').map(Number);
|
|
710
|
+
if (!p[0] || isNaN(p[0]))
|
|
711
|
+
return null;
|
|
712
|
+
return { y: p[0], m: p[1] || 1, d: p[2] || 1 };
|
|
713
|
+
}
|
|
714
|
+
function fmtDT(d) { return `${d.y}-${z(d.m)}-${z(d.d)}`; }
|
|
715
|
+
function daysIn(y, m) { return new Date(y, m, 0).getDate(); }
|
|
716
|
+
function seq(a, b) { const r = []; for (let i = a; i <= b; i++)
|
|
717
|
+
r.push(i); return r; }
|
|
718
|
+
function z(n) { return n < 10 ? `0${n}` : `${n}`; }
|
|
719
|
+
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
720
|
+
function parseTime(s) {
|
|
721
|
+
if (!s)
|
|
722
|
+
return null;
|
|
723
|
+
const p = String(s).split(':').map(Number);
|
|
724
|
+
if (p.length < 2 || isNaN(p[0]) || isNaN(p[1]))
|
|
725
|
+
return null;
|
|
726
|
+
return clamp(p[0], 0, 23) * 60 + clamp(p[1], 0, 59);
|
|
727
|
+
}
|