@kashi-ui/elements 0.0.0-stage → 0.25.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/README.md +45 -2
- package/dist/base.d.ts +47 -0
- package/dist/base.js +148 -0
- package/dist/calendar.d.ts +86 -0
- package/dist/calendar.js +439 -0
- package/dist/cdn/base-Cmq7zeIG.js +1 -0
- package/dist/cdn/calendar-Dm3fq2bs.js +16 -0
- package/dist/cdn/charts-DNOWi4sJ.js +1 -0
- package/dist/cdn/combobox-ZGrZ5okg.js +1 -0
- package/dist/cdn/controls-K-OZcjg4.js +1 -0
- package/dist/cdn/digits-DE1YGT94.js +1 -0
- package/dist/cdn/display-C975tS7j.js +1 -0
- package/dist/cdn/dist-D670Gw0K.js +1 -0
- package/dist/cdn/fields-BUTaSN8m.js +1 -0
- package/dist/cdn/forms-C-effPKn.js +1 -0
- package/dist/cdn/inputs-BjOddAM7.js +1 -0
- package/dist/cdn/jalali-BiM5Kfep.js +1 -0
- package/dist/cdn/kashi-elements.js +1 -0
- package/dist/cdn/map-BHuusa4t.js +12 -0
- package/dist/cdn/menu-DhrqyLgy.js +1 -0
- package/dist/cdn/nav-D_w5BV_K.js +1 -0
- package/dist/cdn/overlay-DX9un2xm.js +1 -0
- package/dist/cdn/pickers-BFLpUruK.js +1 -0
- package/dist/cdn/table-DPQfDMs-.js +1 -0
- package/dist/cdn/text-D1U32D6K.js +1 -0
- package/dist/cdn/words-9OdzG7y6.js +1 -0
- package/dist/cdn.d.ts +3 -0
- package/dist/cdn.js +44 -0
- package/dist/charts.d.ts +106 -0
- package/dist/charts.js +436 -0
- package/dist/combobox.d.ts +106 -0
- package/dist/combobox.js +439 -0
- package/dist/controls.d.ts +82 -0
- package/dist/controls.js +425 -0
- package/dist/display.d.ts +41 -0
- package/dist/display.js +144 -0
- package/dist/fields.d.ts +24 -0
- package/dist/fields.js +103 -0
- package/dist/forms.d.ts +186 -0
- package/dist/forms.js +902 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +32 -0
- package/dist/inputs.d.ts +107 -0
- package/dist/inputs.js +371 -0
- package/dist/iranMapData.d.ts +6 -0
- package/dist/iranMapData.js +37 -0
- package/dist/map.d.ts +64 -0
- package/dist/map.js +416 -0
- package/dist/menu.d.ts +45 -0
- package/dist/menu.js +281 -0
- package/dist/nav.d.ts +163 -0
- package/dist/nav.js +918 -0
- package/dist/overlay.d.ts +79 -0
- package/dist/overlay.js +420 -0
- package/dist/pickers.d.ts +108 -0
- package/dist/pickers.js +655 -0
- package/dist/table.d.ts +53 -0
- package/dist/table.js +267 -0
- package/dist/toast.d.ts +45 -0
- package/dist/toast.js +141 -0
- package/package.json +63 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { HTMLBase } from "./base.js";
|
|
2
|
+
/**
|
|
3
|
+
* پنجرهٔ گفتگو روی <dialog> بومی با showModal: فوکوس داخل پنجره میماند، Esc میبندد،
|
|
4
|
+
* بقیهٔ صفحه از دسترس صفحهخوان خارج میشود و پس از بستن، فوکوس به دکمهٔ بازکننده برمیگردد.
|
|
5
|
+
* محتوای عنصر بدنهٔ پنجره است و عنصرهای slot="footer" در پایین پنجره میآیند.
|
|
6
|
+
*/
|
|
7
|
+
export declare class KashiDialog extends HTMLBase {
|
|
8
|
+
static observedAttributes: string[];
|
|
9
|
+
private dialog;
|
|
10
|
+
private opener;
|
|
11
|
+
private key;
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
|
|
14
|
+
get open(): boolean;
|
|
15
|
+
set open(v: boolean);
|
|
16
|
+
/** باز کردن پنجره */
|
|
17
|
+
show(): void;
|
|
18
|
+
/** بستن پنجره؛ returnValue در رویداد kashi-close میآید */
|
|
19
|
+
close(returnValue?: string): void;
|
|
20
|
+
private build;
|
|
21
|
+
private decorate;
|
|
22
|
+
private sync;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* زبانهها از روی بخشهای داخل عنصر ساخته میشوند: هر فرزند با data-tab یک زبانه است.
|
|
26
|
+
* الگوی Tabs در WAI-ARIA؛ کلیدهای جهتدار در راستبهچپ برعکس عمل میکنند. بدون JavaScript همهٔ بخشها دیده میشوند.
|
|
27
|
+
*/
|
|
28
|
+
export declare class KashiTabs extends HTMLBase {
|
|
29
|
+
static observedAttributes: string[];
|
|
30
|
+
private list;
|
|
31
|
+
private key;
|
|
32
|
+
private ro;
|
|
33
|
+
connectedCallback(): void;
|
|
34
|
+
private setup;
|
|
35
|
+
disconnectedCallback(): void;
|
|
36
|
+
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
|
|
37
|
+
/** مقدار زبانهٔ فعال */
|
|
38
|
+
get value(): string;
|
|
39
|
+
set value(v: string);
|
|
40
|
+
private get panels();
|
|
41
|
+
private build;
|
|
42
|
+
private decorate;
|
|
43
|
+
private select;
|
|
44
|
+
/** نشانگر زبانهٔ فعال که با تغییر زبانه میلغزد */
|
|
45
|
+
private measure;
|
|
46
|
+
private onKey;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* راهنمای کوتاه طبق WCAG 1.4.13: با hover و فوکوس نمایش داده میشود، روی خودش هم hover پذیر است
|
|
50
|
+
* و با Esc بسته میشود. با aria-describedby به اولین فرزند (معمولاً یک دکمه) متصل است.
|
|
51
|
+
*/
|
|
52
|
+
export declare class KashiTooltip extends HTMLBase {
|
|
53
|
+
static observedAttributes: string[];
|
|
54
|
+
private tip;
|
|
55
|
+
private timer;
|
|
56
|
+
private key;
|
|
57
|
+
connectedCallback(): void;
|
|
58
|
+
private setup;
|
|
59
|
+
disconnectedCallback(): void;
|
|
60
|
+
attributeChangedCallback(): void;
|
|
61
|
+
private decorate;
|
|
62
|
+
private show;
|
|
63
|
+
private hide;
|
|
64
|
+
private onKey;
|
|
65
|
+
}
|
|
66
|
+
/** دکمهٔ کپی در کلیپبورد با بازخورد «کپی شد» برای چشم و صفحهخوان */
|
|
67
|
+
export declare class KashiCopyButton extends HTMLBase {
|
|
68
|
+
static observedAttributes: string[];
|
|
69
|
+
private state;
|
|
70
|
+
private timer;
|
|
71
|
+
private built;
|
|
72
|
+
connectedCallback(): void;
|
|
73
|
+
disconnectedCallback(): void;
|
|
74
|
+
attributeChangedCallback(): void;
|
|
75
|
+
/** متنی که کپی میشود: ویژگی value، یا مقدار/متن عنصری که target به آن اشاره میکند */
|
|
76
|
+
get text(): string;
|
|
77
|
+
copy(): Promise<void>;
|
|
78
|
+
private render;
|
|
79
|
+
}
|
package/dist/overlay.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { HTMLBase, ICONS, afterParse, bool, define, emit, esc, uid } from "./base.js";
|
|
2
|
+
/**
|
|
3
|
+
* پنجرهٔ گفتگو روی <dialog> بومی با showModal: فوکوس داخل پنجره میماند، Esc میبندد،
|
|
4
|
+
* بقیهٔ صفحه از دسترس صفحهخوان خارج میشود و پس از بستن، فوکوس به دکمهٔ بازکننده برمیگردد.
|
|
5
|
+
* محتوای عنصر بدنهٔ پنجره است و عنصرهای slot="footer" در پایین پنجره میآیند.
|
|
6
|
+
*/
|
|
7
|
+
export class KashiDialog extends HTMLBase {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.dialog = null;
|
|
11
|
+
this.opener = null;
|
|
12
|
+
this.key = uid("kashi-dialog");
|
|
13
|
+
}
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
afterParse(() => {
|
|
16
|
+
if (!this.isConnected)
|
|
17
|
+
return;
|
|
18
|
+
if (!this.dialog)
|
|
19
|
+
this.build();
|
|
20
|
+
this.sync();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
attributeChangedCallback(name, old, next) {
|
|
24
|
+
if (!this.dialog || old === next)
|
|
25
|
+
return;
|
|
26
|
+
if (name === "open")
|
|
27
|
+
this.sync();
|
|
28
|
+
else
|
|
29
|
+
this.decorate();
|
|
30
|
+
}
|
|
31
|
+
get open() {
|
|
32
|
+
return this.hasAttribute("open");
|
|
33
|
+
}
|
|
34
|
+
set open(v) {
|
|
35
|
+
this.toggleAttribute("open", !!v);
|
|
36
|
+
}
|
|
37
|
+
/** باز کردن پنجره */
|
|
38
|
+
show() {
|
|
39
|
+
this.open = true;
|
|
40
|
+
}
|
|
41
|
+
/** بستن پنجره؛ returnValue در رویداد kashi-close میآید */
|
|
42
|
+
close(returnValue) {
|
|
43
|
+
if (this.dialog?.open)
|
|
44
|
+
this.dialog.close(returnValue);
|
|
45
|
+
else
|
|
46
|
+
this.open = false;
|
|
47
|
+
}
|
|
48
|
+
build() {
|
|
49
|
+
const footer = [...this.children].filter((c) => c.getAttribute("slot") === "footer");
|
|
50
|
+
const body = [...this.childNodes].filter((n) => !footer.includes(n));
|
|
51
|
+
const d = document.createElement("dialog");
|
|
52
|
+
d.setAttribute("aria-labelledby", `${this.key}-title`);
|
|
53
|
+
d.innerHTML = `<div class="k-dialog__header"><div class="k-dialog__heading"><h2 class="k-dialog__title" id="${this.key}-title"></h2><p class="k-dialog__description" id="${this.key}-desc"></p></div><button type="button" class="k-btn k-btn--tertiary k-btn--icon k-btn--sm k-dialog__close">${ICONS.close}</button></div>`;
|
|
54
|
+
const hasBody = body.some((n) => n.nodeType === 1 || n.textContent?.trim());
|
|
55
|
+
if (hasBody) {
|
|
56
|
+
const b = document.createElement("div");
|
|
57
|
+
b.className = "k-dialog__body";
|
|
58
|
+
b.append(...body);
|
|
59
|
+
d.append(b);
|
|
60
|
+
}
|
|
61
|
+
if (footer.length) {
|
|
62
|
+
const f = document.createElement("div");
|
|
63
|
+
f.className = "k-dialog__footer";
|
|
64
|
+
for (const el of footer) {
|
|
65
|
+
el.removeAttribute("slot");
|
|
66
|
+
f.append(el);
|
|
67
|
+
}
|
|
68
|
+
d.append(f);
|
|
69
|
+
}
|
|
70
|
+
this.replaceChildren(d);
|
|
71
|
+
this.dialog = d;
|
|
72
|
+
d.querySelector(".k-dialog__close").addEventListener("click", () => this.close());
|
|
73
|
+
d.addEventListener("click", (e) => {
|
|
74
|
+
// کلیک روی خود <dialog> یعنی کلیک روی پسزمینه
|
|
75
|
+
if (e.target === d && bool(this, "dismissible", true))
|
|
76
|
+
this.close();
|
|
77
|
+
const closer = e.target.closest("[data-kashi-close]");
|
|
78
|
+
if (closer && d.contains(closer))
|
|
79
|
+
this.close(closer.getAttribute("data-kashi-close") || undefined);
|
|
80
|
+
});
|
|
81
|
+
d.addEventListener("close", () => {
|
|
82
|
+
this.removeAttribute("open");
|
|
83
|
+
this.opener?.focus?.();
|
|
84
|
+
emit(this, "kashi-close", { returnValue: d.returnValue });
|
|
85
|
+
});
|
|
86
|
+
this.decorate();
|
|
87
|
+
}
|
|
88
|
+
decorate() {
|
|
89
|
+
const d = this.dialog;
|
|
90
|
+
const size = this.getAttribute("size") ?? "md";
|
|
91
|
+
const placement = this.getAttribute("placement") ?? "center";
|
|
92
|
+
d.className = ["k-dialog", size !== "md" && `k-dialog--${size}`, placement !== "center" && `k-dialog--${placement}`, this.getAttribute("tone") === "danger" && "k-dialog--danger"].filter(Boolean).join(" ");
|
|
93
|
+
d.dataset.placement = placement;
|
|
94
|
+
d.querySelector(".k-dialog__title").textContent = this.getAttribute("heading") ?? "";
|
|
95
|
+
const desc = d.querySelector(".k-dialog__description");
|
|
96
|
+
const description = this.getAttribute("description");
|
|
97
|
+
desc.textContent = description ?? "";
|
|
98
|
+
desc.hidden = !description;
|
|
99
|
+
if (description)
|
|
100
|
+
d.setAttribute("aria-describedby", desc.id);
|
|
101
|
+
else
|
|
102
|
+
d.removeAttribute("aria-describedby");
|
|
103
|
+
d.querySelector(".k-dialog__close").setAttribute("aria-label", this.getAttribute("close-label") ?? "بستن");
|
|
104
|
+
}
|
|
105
|
+
sync() {
|
|
106
|
+
const d = this.dialog;
|
|
107
|
+
if (!d || !this.isConnected)
|
|
108
|
+
return;
|
|
109
|
+
if (this.open && !d.open) {
|
|
110
|
+
this.opener = document.activeElement;
|
|
111
|
+
d.showModal?.();
|
|
112
|
+
emit(this, "kashi-open", {});
|
|
113
|
+
}
|
|
114
|
+
else if (!this.open && d.open)
|
|
115
|
+
d.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
KashiDialog.observedAttributes = ["open", "heading", "description", "tone", "size", "placement", "close-label"];
|
|
119
|
+
/**
|
|
120
|
+
* زبانهها از روی بخشهای داخل عنصر ساخته میشوند: هر فرزند با data-tab یک زبانه است.
|
|
121
|
+
* الگوی Tabs در WAI-ARIA؛ کلیدهای جهتدار در راستبهچپ برعکس عمل میکنند. بدون JavaScript همهٔ بخشها دیده میشوند.
|
|
122
|
+
*/
|
|
123
|
+
export class KashiTabs extends HTMLBase {
|
|
124
|
+
constructor() {
|
|
125
|
+
super(...arguments);
|
|
126
|
+
this.list = null;
|
|
127
|
+
this.key = uid("kashi-tabs");
|
|
128
|
+
this.ro = null;
|
|
129
|
+
}
|
|
130
|
+
connectedCallback() {
|
|
131
|
+
afterParse(() => this.isConnected && this.setup());
|
|
132
|
+
}
|
|
133
|
+
setup() {
|
|
134
|
+
if (!this.list)
|
|
135
|
+
this.build();
|
|
136
|
+
this.decorate();
|
|
137
|
+
this.select(this.getAttribute("value") ?? this.panels[0]?.dataset.value ?? "", false);
|
|
138
|
+
if (typeof ResizeObserver !== "undefined" && !this.ro) {
|
|
139
|
+
this.ro = new ResizeObserver(() => this.measure());
|
|
140
|
+
this.ro.observe(this.list);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
disconnectedCallback() {
|
|
144
|
+
this.ro?.disconnect();
|
|
145
|
+
this.ro = null;
|
|
146
|
+
}
|
|
147
|
+
attributeChangedCallback(name, old, next) {
|
|
148
|
+
if (!this.list || old === next)
|
|
149
|
+
return;
|
|
150
|
+
if (name === "value")
|
|
151
|
+
this.select(next ?? "", false);
|
|
152
|
+
else
|
|
153
|
+
this.decorate();
|
|
154
|
+
}
|
|
155
|
+
/** مقدار زبانهٔ فعال */
|
|
156
|
+
get value() {
|
|
157
|
+
return this.getAttribute("value") ?? "";
|
|
158
|
+
}
|
|
159
|
+
set value(v) {
|
|
160
|
+
this.setAttribute("value", v);
|
|
161
|
+
}
|
|
162
|
+
get panels() {
|
|
163
|
+
return [...this.children].filter((c) => c instanceof HTMLElement && c.hasAttribute("data-tab"));
|
|
164
|
+
}
|
|
165
|
+
build() {
|
|
166
|
+
const list = document.createElement("div");
|
|
167
|
+
list.setAttribute("role", "tablist");
|
|
168
|
+
list.className = "k-tabs__list";
|
|
169
|
+
this.panels.forEach((p, i) => {
|
|
170
|
+
const value = p.dataset.value || String(i + 1);
|
|
171
|
+
p.dataset.value = value;
|
|
172
|
+
const tab = document.createElement("button");
|
|
173
|
+
tab.type = "button";
|
|
174
|
+
tab.setAttribute("role", "tab");
|
|
175
|
+
tab.className = "k-tabs__tab";
|
|
176
|
+
tab.id = `${this.key}-tab-${i}`;
|
|
177
|
+
tab.dataset.value = value;
|
|
178
|
+
tab.textContent = p.dataset.tab ?? "";
|
|
179
|
+
if (p.hasAttribute("data-disabled"))
|
|
180
|
+
tab.disabled = true;
|
|
181
|
+
p.id || (p.id = `${this.key}-panel-${i}`);
|
|
182
|
+
tab.setAttribute("aria-controls", p.id);
|
|
183
|
+
p.setAttribute("role", "tabpanel");
|
|
184
|
+
p.setAttribute("aria-labelledby", tab.id);
|
|
185
|
+
p.tabIndex = 0;
|
|
186
|
+
p.classList.add("k-tabs__panel");
|
|
187
|
+
list.append(tab);
|
|
188
|
+
});
|
|
189
|
+
const indicator = document.createElement("span");
|
|
190
|
+
indicator.className = "k-tabs__indicator";
|
|
191
|
+
indicator.setAttribute("aria-hidden", "true");
|
|
192
|
+
list.append(indicator);
|
|
193
|
+
list.addEventListener("click", (e) => {
|
|
194
|
+
const tab = e.target.closest('[role="tab"]');
|
|
195
|
+
if (tab && !tab.disabled)
|
|
196
|
+
this.select(tab.dataset.value, true);
|
|
197
|
+
});
|
|
198
|
+
list.addEventListener("keydown", (e) => this.onKey(e));
|
|
199
|
+
this.prepend(list);
|
|
200
|
+
this.list = list;
|
|
201
|
+
}
|
|
202
|
+
decorate() {
|
|
203
|
+
const variant = this.getAttribute("variant") ?? "line";
|
|
204
|
+
const vertical = this.getAttribute("orientation") === "vertical";
|
|
205
|
+
for (const c of [...this.classList])
|
|
206
|
+
if (c.startsWith("k-tabs"))
|
|
207
|
+
this.classList.remove(c);
|
|
208
|
+
this.classList.add("k-tabs", `k-tabs--${variant}`);
|
|
209
|
+
if (bool(this, "block"))
|
|
210
|
+
this.classList.add("k-tabs--block");
|
|
211
|
+
if (vertical)
|
|
212
|
+
this.classList.add("k-tabs--vertical");
|
|
213
|
+
if (this.getAttribute("size") === "sm")
|
|
214
|
+
this.classList.add("k-tabs--sm");
|
|
215
|
+
this.dataset.orientation = vertical ? "vertical" : "horizontal";
|
|
216
|
+
this.list.setAttribute("aria-orientation", vertical ? "vertical" : "horizontal");
|
|
217
|
+
const label = this.getAttribute("label");
|
|
218
|
+
if (label)
|
|
219
|
+
this.list.setAttribute("aria-label", label);
|
|
220
|
+
this.measure();
|
|
221
|
+
}
|
|
222
|
+
select(value, user) {
|
|
223
|
+
const tabs = [...this.list.querySelectorAll('[role="tab"]')];
|
|
224
|
+
if (!tabs.some((t) => t.dataset.value === value))
|
|
225
|
+
value = tabs[0]?.dataset.value ?? "";
|
|
226
|
+
for (const t of tabs) {
|
|
227
|
+
const on = t.dataset.value === value;
|
|
228
|
+
t.setAttribute("aria-selected", String(on));
|
|
229
|
+
t.tabIndex = on ? 0 : -1;
|
|
230
|
+
}
|
|
231
|
+
for (const p of this.panels)
|
|
232
|
+
p.hidden = p.dataset.value !== value;
|
|
233
|
+
if (this.getAttribute("value") !== value) {
|
|
234
|
+
// تغییر ویژگی دوباره select را صدا میزند؛ همان مقدار است و کاری نمیکند
|
|
235
|
+
this.setAttribute("value", value);
|
|
236
|
+
}
|
|
237
|
+
this.measure();
|
|
238
|
+
if (user)
|
|
239
|
+
emit(this, "kashi-change", { value });
|
|
240
|
+
}
|
|
241
|
+
/** نشانگر زبانهٔ فعال که با تغییر زبانه میلغزد */
|
|
242
|
+
measure() {
|
|
243
|
+
const list = this.list;
|
|
244
|
+
const tab = list?.querySelector('[role="tab"][aria-selected="true"]');
|
|
245
|
+
if (!list || !tab || !tab.offsetWidth) {
|
|
246
|
+
list?.removeAttribute("data-indicator");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
list.setAttribute("data-indicator", "");
|
|
250
|
+
list.style.setProperty("--_ix", `${tab.offsetLeft}px`);
|
|
251
|
+
list.style.setProperty("--_iy", `${tab.offsetTop}px`);
|
|
252
|
+
list.style.setProperty("--_iw", `${tab.offsetWidth}px`);
|
|
253
|
+
list.style.setProperty("--_ih", `${tab.offsetHeight}px`);
|
|
254
|
+
}
|
|
255
|
+
onKey(e) {
|
|
256
|
+
const tabs = [...this.list.querySelectorAll('[role="tab"]:not(:disabled)')];
|
|
257
|
+
const i = tabs.indexOf(document.activeElement);
|
|
258
|
+
if (i < 0)
|
|
259
|
+
return;
|
|
260
|
+
const rtl = getComputedStyle(this.list).direction === "rtl";
|
|
261
|
+
const moves = this.getAttribute("orientation") === "vertical"
|
|
262
|
+
? { ArrowDown: i + 1, ArrowUp: i - 1, Home: 0, End: tabs.length - 1 }
|
|
263
|
+
: { ArrowLeft: rtl ? i + 1 : i - 1, ArrowRight: rtl ? i - 1 : i + 1, Home: 0, End: tabs.length - 1 };
|
|
264
|
+
if (!(e.key in moves))
|
|
265
|
+
return;
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
const next = tabs[(moves[e.key] + tabs.length) % tabs.length];
|
|
268
|
+
next.focus();
|
|
269
|
+
this.select(next.dataset.value, true);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
KashiTabs.observedAttributes = ["value", "variant", "block", "orientation", "size", "label"];
|
|
273
|
+
/**
|
|
274
|
+
* راهنمای کوتاه طبق WCAG 1.4.13: با hover و فوکوس نمایش داده میشود، روی خودش هم hover پذیر است
|
|
275
|
+
* و با Esc بسته میشود. با aria-describedby به اولین فرزند (معمولاً یک دکمه) متصل است.
|
|
276
|
+
*/
|
|
277
|
+
export class KashiTooltip extends HTMLBase {
|
|
278
|
+
constructor() {
|
|
279
|
+
super(...arguments);
|
|
280
|
+
this.tip = null;
|
|
281
|
+
this.key = uid("kashi-tip");
|
|
282
|
+
this.onKey = (e) => {
|
|
283
|
+
if (e.key === "Escape") {
|
|
284
|
+
clearTimeout(this.timer);
|
|
285
|
+
this.tip.hidden = true;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
connectedCallback() {
|
|
290
|
+
afterParse(() => this.isConnected && this.setup());
|
|
291
|
+
}
|
|
292
|
+
setup() {
|
|
293
|
+
if (!this.tip) {
|
|
294
|
+
this.classList.add("k-tooltip-anchor");
|
|
295
|
+
const tip = document.createElement("span");
|
|
296
|
+
tip.setAttribute("role", "tooltip");
|
|
297
|
+
tip.id = this.key;
|
|
298
|
+
tip.hidden = true;
|
|
299
|
+
this.append(tip);
|
|
300
|
+
this.tip = tip;
|
|
301
|
+
const trigger = this.firstElementChild !== tip ? this.firstElementChild : null;
|
|
302
|
+
if (trigger)
|
|
303
|
+
trigger.setAttribute("aria-describedby", [trigger.getAttribute("aria-describedby"), this.key].filter(Boolean).join(" "));
|
|
304
|
+
this.addEventListener("mouseenter", () => this.show(Number(this.getAttribute("delay") ?? 300)));
|
|
305
|
+
this.addEventListener("mouseleave", () => this.hide());
|
|
306
|
+
this.addEventListener("focusin", () => this.show(0));
|
|
307
|
+
this.addEventListener("focusout", () => this.hide());
|
|
308
|
+
}
|
|
309
|
+
this.decorate();
|
|
310
|
+
}
|
|
311
|
+
disconnectedCallback() {
|
|
312
|
+
clearTimeout(this.timer);
|
|
313
|
+
document.removeEventListener("keydown", this.onKey);
|
|
314
|
+
}
|
|
315
|
+
attributeChangedCallback() {
|
|
316
|
+
if (this.tip)
|
|
317
|
+
this.decorate();
|
|
318
|
+
}
|
|
319
|
+
decorate() {
|
|
320
|
+
const placement = this.getAttribute("placement");
|
|
321
|
+
this.tip.className = `k-tooltip${placement && placement !== "top" ? ` k-tooltip--${placement}` : ""}`;
|
|
322
|
+
this.tip.textContent = this.getAttribute("content") ?? "";
|
|
323
|
+
}
|
|
324
|
+
show(wait) {
|
|
325
|
+
clearTimeout(this.timer);
|
|
326
|
+
this.timer = setTimeout(() => {
|
|
327
|
+
this.tip.hidden = false;
|
|
328
|
+
document.addEventListener("keydown", this.onKey);
|
|
329
|
+
}, wait);
|
|
330
|
+
}
|
|
331
|
+
hide() {
|
|
332
|
+
clearTimeout(this.timer);
|
|
333
|
+
this.timer = setTimeout(() => {
|
|
334
|
+
this.tip.hidden = true;
|
|
335
|
+
document.removeEventListener("keydown", this.onKey);
|
|
336
|
+
}, 100);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
KashiTooltip.observedAttributes = ["content", "placement"];
|
|
340
|
+
/** دکمهٔ کپی در کلیپبورد با بازخورد «کپی شد» برای چشم و صفحهخوان */
|
|
341
|
+
export class KashiCopyButton extends HTMLBase {
|
|
342
|
+
constructor() {
|
|
343
|
+
super(...arguments);
|
|
344
|
+
this.state = "idle";
|
|
345
|
+
this.built = false;
|
|
346
|
+
}
|
|
347
|
+
connectedCallback() {
|
|
348
|
+
if (!this.built) {
|
|
349
|
+
this.built = true;
|
|
350
|
+
this.addEventListener("click", (e) => {
|
|
351
|
+
if (e.target.closest("button"))
|
|
352
|
+
this.copy();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
this.render();
|
|
356
|
+
}
|
|
357
|
+
disconnectedCallback() {
|
|
358
|
+
clearTimeout(this.timer);
|
|
359
|
+
}
|
|
360
|
+
attributeChangedCallback() {
|
|
361
|
+
if (this.built)
|
|
362
|
+
this.render();
|
|
363
|
+
}
|
|
364
|
+
/** متنی که کپی میشود: ویژگی value، یا مقدار/متن عنصری که target به آن اشاره میکند */
|
|
365
|
+
get text() {
|
|
366
|
+
const value = this.getAttribute("value");
|
|
367
|
+
if (value !== null)
|
|
368
|
+
return value;
|
|
369
|
+
const target = this.getAttribute("target");
|
|
370
|
+
const el = target ? document.querySelector(target) : null;
|
|
371
|
+
return el ? (typeof el.value === "string" ? el.value : (el.textContent ?? "").trim()) : "";
|
|
372
|
+
}
|
|
373
|
+
async copy() {
|
|
374
|
+
try {
|
|
375
|
+
await navigator.clipboard.writeText(this.text);
|
|
376
|
+
this.state = "copied";
|
|
377
|
+
emit(this, "kashi-copy", { value: this.text });
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
this.state = "failed";
|
|
381
|
+
}
|
|
382
|
+
this.render();
|
|
383
|
+
clearTimeout(this.timer);
|
|
384
|
+
this.timer = setTimeout(() => {
|
|
385
|
+
this.state = "idle";
|
|
386
|
+
this.render();
|
|
387
|
+
}, Number(this.getAttribute("timeout")) || 2000);
|
|
388
|
+
}
|
|
389
|
+
render() {
|
|
390
|
+
const copy = this.getAttribute("label") ?? "کپی";
|
|
391
|
+
const copied = this.getAttribute("copied-label") ?? "کپی شد";
|
|
392
|
+
const text = this.state === "copied" ? copied : this.state === "failed" ? "کپی نشد" : copy;
|
|
393
|
+
const iconOnly = bool(this, "icon-only");
|
|
394
|
+
const icon = this.state === "copied" ? ICONS.check : ICONS.copy;
|
|
395
|
+
const variant = this.getAttribute("variant") ?? "secondary";
|
|
396
|
+
const size = this.getAttribute("size") ?? "sm";
|
|
397
|
+
const focused = this.contains(document.activeElement);
|
|
398
|
+
this.innerHTML = `<button type="button" class="k-btn k-btn--${esc(variant)}${size !== "md" ? ` k-btn--${esc(size)}` : ""}${iconOnly ? " k-btn--icon" : ""}" data-variant="${esc(variant)}" data-size="${esc(size)}"${iconOnly ? ` aria-label="${esc(text)}"` : ""}${this.state === "copied" ? ' data-copied=""' : ""}>${icon}${iconOnly ? "" : esc(text)}</button><span class="k-sr-only" aria-live="polite">${this.state === "idle" ? "" : esc(text)}</span>`;
|
|
399
|
+
if (focused)
|
|
400
|
+
this.querySelector("button").focus();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
KashiCopyButton.observedAttributes = ["value", "target", "label", "copied-label", "icon-only", "variant", "size"];
|
|
404
|
+
define("kashi-dialog", KashiDialog, "contents");
|
|
405
|
+
define("kashi-tabs", KashiTabs, "block");
|
|
406
|
+
define("kashi-tooltip", KashiTooltip);
|
|
407
|
+
define("kashi-copy-button", KashiCopyButton);
|
|
408
|
+
// دکمههایی که بدون JavaScript پنجره را باز یا میبندند: data-kashi-open="شناسهٔ پنجره"
|
|
409
|
+
if (typeof document !== "undefined") {
|
|
410
|
+
document.addEventListener("click", (e) => {
|
|
411
|
+
const opener = e.target?.closest?.("[data-kashi-open]");
|
|
412
|
+
if (opener) {
|
|
413
|
+
const dialog = document.getElementById(opener.getAttribute("data-kashi-open"));
|
|
414
|
+
if (dialog?.show) {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
dialog.show();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { HTMLBase } from "./base.js";
|
|
2
|
+
import "./calendar.js";
|
|
3
|
+
interface Range {
|
|
4
|
+
start: Date | null;
|
|
5
|
+
end: Date | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* انتخاب بازهٔ تاریخ شمسی با تقویم و میانبرها (۷ روز اخیر، این ماه، …).
|
|
9
|
+
* با name دو فیلد name_start و name_end با تاریخ میلادی YYYY-MM-DD ارسال میشوند.
|
|
10
|
+
*/
|
|
11
|
+
export declare class KashiDateRangePicker extends HTMLBase {
|
|
12
|
+
static observedAttributes: string[];
|
|
13
|
+
private current;
|
|
14
|
+
private draft;
|
|
15
|
+
private valueSet;
|
|
16
|
+
private rendered;
|
|
17
|
+
private key;
|
|
18
|
+
private trigger;
|
|
19
|
+
private pop;
|
|
20
|
+
connectedCallback(): void;
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
|
|
23
|
+
/** { start, end } با Date */
|
|
24
|
+
get value(): Range;
|
|
25
|
+
set value(r: Range);
|
|
26
|
+
private fmt;
|
|
27
|
+
private text;
|
|
28
|
+
private render;
|
|
29
|
+
open(): void;
|
|
30
|
+
private paintDraft;
|
|
31
|
+
close(focus?: boolean): void;
|
|
32
|
+
private onOutside;
|
|
33
|
+
}
|
|
34
|
+
/** انتخاب ماه شمسی برای گزارشها و فیلترها؛ مقدار «1405-01» (سال-ماه شمسی) */
|
|
35
|
+
export declare class KashiMonthPicker extends HTMLBase {
|
|
36
|
+
static observedAttributes: string[];
|
|
37
|
+
private selected;
|
|
38
|
+
private year;
|
|
39
|
+
private focusMonth;
|
|
40
|
+
private valueSet;
|
|
41
|
+
private rendered;
|
|
42
|
+
connectedCallback(): void;
|
|
43
|
+
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
|
|
44
|
+
private parse;
|
|
45
|
+
/** «1405-01» */
|
|
46
|
+
get value(): string;
|
|
47
|
+
set value(v: string);
|
|
48
|
+
private out;
|
|
49
|
+
private render;
|
|
50
|
+
}
|
|
51
|
+
/** حجم فایل به فارسی: «۱٫۲ مگابایت» */
|
|
52
|
+
export declare function formatBytes(bytes: number): string;
|
|
53
|
+
/**
|
|
54
|
+
* بارگذاری فایل با کشیدن و رها کردن، بررسی نوع و حجم و فهرست فایلها.
|
|
55
|
+
* با name فایلها با فرم HTML ارسال میشوند؛ برای بارگذاری فوری، element.uploader = (file, onProgress) => Promise بدهید.
|
|
56
|
+
*/
|
|
57
|
+
export declare class KashiFileUpload extends HTMLBase {
|
|
58
|
+
static observedAttributes: string[];
|
|
59
|
+
/** بارگذاری هر فایل؛ onProgress(درصد) را صدا بزنید */
|
|
60
|
+
uploader?: (file: File, onProgress: (percent: number) => void) => Promise<void>;
|
|
61
|
+
private items;
|
|
62
|
+
private rendered;
|
|
63
|
+
private key;
|
|
64
|
+
private input;
|
|
65
|
+
connectedCallback(): void;
|
|
66
|
+
attributeChangedCallback(_n: string, old: string | null, next: string | null): void;
|
|
67
|
+
/** فایلهای پذیرفتهشده */
|
|
68
|
+
get files(): File[];
|
|
69
|
+
private get multiple();
|
|
70
|
+
private render;
|
|
71
|
+
private accepts;
|
|
72
|
+
private add;
|
|
73
|
+
private announce;
|
|
74
|
+
private changed;
|
|
75
|
+
private paint;
|
|
76
|
+
}
|
|
77
|
+
/** نسبت کنتراست دو رنگ طبق WCAG */
|
|
78
|
+
export declare const contrastRatio: (a: string, b: string) => number;
|
|
79
|
+
/**
|
|
80
|
+
* انتخاب رنگ: رنگهای پیشنهادی، پنل اشباع و رنگمایه، کد HEX و RGB، و کنتراست با متن سفید یا مشکی.
|
|
81
|
+
* رنگهای پیشنهادی دلخواه با swatches="#0b8077 فیروزهای, #2740ed لاجوردی".
|
|
82
|
+
*/
|
|
83
|
+
export declare class KashiColorPicker extends HTMLBase {
|
|
84
|
+
static observedAttributes: string[];
|
|
85
|
+
private current;
|
|
86
|
+
private hsv;
|
|
87
|
+
private valueSet;
|
|
88
|
+
private rendered;
|
|
89
|
+
private key;
|
|
90
|
+
private panel;
|
|
91
|
+
connectedCallback(): void;
|
|
92
|
+
disconnectedCallback(): void;
|
|
93
|
+
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
|
|
94
|
+
/** کد HEX، مثل «#0b8077» */
|
|
95
|
+
get value(): string;
|
|
96
|
+
set value(v: string);
|
|
97
|
+
private setColor;
|
|
98
|
+
private swatches;
|
|
99
|
+
private render;
|
|
100
|
+
private pick;
|
|
101
|
+
private paint;
|
|
102
|
+
private paintMessages;
|
|
103
|
+
private openPanel;
|
|
104
|
+
private paintPanel;
|
|
105
|
+
private closePanel;
|
|
106
|
+
private onOutside;
|
|
107
|
+
}
|
|
108
|
+
export {};
|