@kashi-ui/elements 0.26.0 → 1.1.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.
Files changed (72) hide show
  1. package/README.md +9 -0
  2. package/dist/appointment.js +13 -12
  3. package/dist/base.d.ts +408 -0
  4. package/dist/base.js +29 -0
  5. package/dist/basic.js +8 -9
  6. package/dist/calendar.js +21 -20
  7. package/dist/cdn/appointment-CrIZi8Bp.js +1 -0
  8. package/dist/cdn/base-DTQ50VdG.js +1 -0
  9. package/dist/cdn/basic-CrEW2RKF.js +1 -0
  10. package/dist/cdn/calendar-CYRrdGIf.js +16 -0
  11. package/dist/cdn/charts-D6KM26Vp.js +1 -0
  12. package/dist/cdn/combobox-6EFI2qqN.js +1 -0
  13. package/dist/cdn/controls-BwebEIlf.js +1 -0
  14. package/dist/cdn/display-DFuMkcj5.js +1 -0
  15. package/dist/cdn/fields-DhLIhHQ8.js +1 -0
  16. package/dist/cdn/forms-BHivKDdC.js +1 -0
  17. package/dist/cdn/holidays-DgbIAdlt.js +1 -0
  18. package/dist/cdn/inputs-R7nEHfbV.js +1 -0
  19. package/dist/cdn/kashi-elements.js +1 -1
  20. package/dist/cdn/map-H3xiZd8Y.js +12 -0
  21. package/dist/cdn/{menu-Cga7QOfs.js → menu-BAUSubXk.js} +1 -1
  22. package/dist/cdn/nav-NK-v48B5.js +1 -0
  23. package/dist/cdn/overlay-CBv8grz3.js +1 -0
  24. package/dist/cdn/pickers-BHzNt_X8.js +1 -0
  25. package/dist/cdn/table-Bt9WVLoo.js +1 -0
  26. package/dist/cdn/text-D4a_hlwL.js +1 -0
  27. package/dist/cdn/time-DJ3p1wvd.js +1 -0
  28. package/dist/cdn/words-BdADG_9U.js +1 -0
  29. package/dist/cdn.d.ts +1 -0
  30. package/dist/cdn.js +2 -0
  31. package/dist/charts.js +27 -24
  32. package/dist/combobox.d.ts +6 -5
  33. package/dist/combobox.js +29 -20
  34. package/dist/controls.js +15 -15
  35. package/dist/display.js +17 -14
  36. package/dist/fields.js +3 -3
  37. package/dist/forms.d.ts +3 -3
  38. package/dist/forms.js +54 -47
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +3 -0
  41. package/dist/inputs.d.ts +3 -3
  42. package/dist/inputs.js +31 -30
  43. package/dist/map.js +16 -13
  44. package/dist/nav.js +18 -16
  45. package/dist/overlay.js +5 -5
  46. package/dist/pickers.d.ts +1 -1
  47. package/dist/pickers.js +39 -38
  48. package/dist/table.js +7 -7
  49. package/dist/toast.js +3 -3
  50. package/package.json +4 -4
  51. package/dist/cdn/appointment-DkMxtSWi.js +0 -1
  52. package/dist/cdn/base-DfzNTdQk.js +0 -1
  53. package/dist/cdn/basic-B5n1okZO.js +0 -1
  54. package/dist/cdn/calendar-oMlnK1_D.js +0 -16
  55. package/dist/cdn/charts-SHKq2S4I.js +0 -1
  56. package/dist/cdn/combobox-DiGy7lAB.js +0 -1
  57. package/dist/cdn/controls-C6zwNYWs.js +0 -1
  58. package/dist/cdn/digits-DE1YGT94.js +0 -1
  59. package/dist/cdn/display-DZFW9EEX.js +0 -1
  60. package/dist/cdn/fields-D2hmrvVR.js +0 -1
  61. package/dist/cdn/forms-CeItYHFx.js +0 -1
  62. package/dist/cdn/holidays-BwsAEOI1.js +0 -1
  63. package/dist/cdn/inputs-DuP7P3ka.js +0 -1
  64. package/dist/cdn/jalali-BiM5Kfep.js +0 -1
  65. package/dist/cdn/map-w5X2fTQI.js +0 -12
  66. package/dist/cdn/nav-CwPZ20z-.js +0 -1
  67. package/dist/cdn/overlay-BDzwsBN5.js +0 -1
  68. package/dist/cdn/pickers-BOpaZ-7q.js +0 -1
  69. package/dist/cdn/table-BdF43L5q.js +0 -1
  70. package/dist/cdn/text-D1U32D6K.js +0 -1
  71. package/dist/cdn/time-DXw4vOne.js +0 -1
  72. package/dist/cdn/words-9OdzG7y6.js +0 -1
@@ -32,8 +32,8 @@ export declare class KashiCombobox extends HTMLBase {
32
32
  private listbox;
33
33
  private live;
34
34
  /** برچسب پیش‌فرض وقتی label داده نشود */
35
- protected defaultLabel: string | null;
36
- protected defaultPlaceholder: string;
35
+ protected get defaultLabel(): string | null;
36
+ protected get defaultPlaceholder(): string;
37
37
  connectedCallback(): void;
38
38
  disconnectedCallback(): void;
39
39
  attributeChangedCallback(name: string, old: string | null, next: string | null): void;
@@ -71,8 +71,8 @@ export declare class KashiCombobox extends HTMLBase {
71
71
  /** انتخاب یکی از ۳۱ استان با جستجو؛ با نام مرکز استان هم پیدا می‌شود (مثلاً «مشهد») */
72
72
  export declare class KashiProvinceSelect extends KashiCombobox {
73
73
  static observedAttributes: string[];
74
- protected defaultLabel: string;
75
- protected defaultPlaceholder: string;
74
+ protected get defaultLabel(): string;
75
+ protected get defaultPlaceholder(): string;
76
76
  protected source(): ComboboxOption[];
77
77
  protected detail(): {
78
78
  value: string;
@@ -85,7 +85,8 @@ export declare class KashiProvinceSelect extends KashiCombobox {
85
85
  */
86
86
  export declare class KashiCitySelect extends KashiCombobox {
87
87
  static observedAttributes: string[];
88
- protected defaultLabel: string;
88
+ protected get defaultLabel(): string;
89
+ protected get defaultPlaceholder(): string;
89
90
  private custom;
90
91
  get province(): string | null;
91
92
  set province(p: string | null);
package/dist/combobox.js CHANGED
@@ -1,5 +1,5 @@
1
- import { CITIES, PROVINCES, getCities, normalizePersian, toFaDigits } from "@kashi-ui/persian";
2
- import { HTMLBase, afterParse, define, emit, esc, fieldHTML, setMessages, syncHidden, uid } from "./base.js";
1
+ import { CITIES, PROVINCES, getCities, normalizePersian } from "@kashi-ui/persian";
2
+ import { afterParse, define, emit, esc, fieldHTML, HTMLBase, msg, num, setMessages, syncHidden, uid } from "./base.js";
3
3
  /** متن یکدست برای جستجو: ي/ی، ك/ک، ارقام، حروف کوچک */
4
4
  export const searchText = (s) => normalizePersian(s, { digits: "latin" }).toLowerCase();
5
5
  /** بیشترین تعداد گزینهٔ رسم‌شده؛ برای فهرست‌های بلند مثل ۱۲۰۰ شهر، بقیه با تایپ پیدا می‌شوند */
@@ -24,14 +24,18 @@ export class KashiCombobox extends HTMLBase {
24
24
  this.rendered = false;
25
25
  this.valueSet = false;
26
26
  this.key = uid("kashi-combo");
27
- /** برچسب پیش‌فرض وقتی label داده نشود */
28
- this.defaultLabel = null;
29
- this.defaultPlaceholder = "";
30
27
  this.onOutside = (e) => {
31
28
  if (!this.contains(e.target))
32
29
  this.close();
33
30
  };
34
31
  }
32
+ /** برچسب پیش‌فرض وقتی label داده نشود */
33
+ get defaultLabel() {
34
+ return null;
35
+ }
36
+ get defaultPlaceholder() {
37
+ return "";
38
+ }
35
39
  connectedCallback() {
36
40
  afterParse(() => {
37
41
  if (!this.isConnected)
@@ -135,7 +139,7 @@ export class KashiCombobox extends HTMLBase {
135
139
  const required = this.hasAttribute("required");
136
140
  const disabled = this.hasAttribute("disabled");
137
141
  const listId = `${id}-list`;
138
- const control = `<div class="k-combobox"><div class="k-input"${disabled ? ' data-state="disabled"' : ""}><input id="${id}" class="k-input__control" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="${listId}" autocomplete="off" placeholder="${esc(this.getAttribute("placeholder") ?? this.defaultPlaceholder)}"${required ? " required" : ""}${disabled ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}><button type="button" tabindex="-1" class="k-input__action k-combobox__toggle" aria-label="نمایش گزینه‌ها"${disabled ? " disabled" : ""}>${CHEVRON}</button></div><ul id="${listId}" role="listbox" class="k-listbox" hidden style="inset-block-start: calc(100% + 6px)"${label ? ` aria-label="${esc(label)}"` : ` aria-labelledby="${id}"`}></ul><span class="k-sr-only" aria-live="polite"></span></div>`;
142
+ const control = `<div class="k-combobox"><div class="k-input"${disabled ? ' data-state="disabled"' : ""}><input id="${id}" class="k-input__control" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="${listId}" autocomplete="off" placeholder="${esc(this.getAttribute("placeholder") ?? this.defaultPlaceholder)}"${required ? " required" : ""}${disabled ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}><button type="button" tabindex="-1" class="k-input__action k-combobox__toggle" aria-label="${esc(msg().combobox.toggle)}"${disabled ? " disabled" : ""}>${CHEVRON}</button></div><ul id="${listId}" role="listbox" class="k-listbox" hidden style="inset-block-start: calc(100% + 6px)"${label ? ` aria-label="${esc(label)}"` : ` aria-labelledby="${id}"`}></ul><span class="k-sr-only" aria-live="polite"></span></div>`;
139
143
  this.querySelector(":scope > .k-field")?.remove();
140
144
  this.insertAdjacentHTML("afterbegin", fieldHTML({ id, label, required, control }));
141
145
  this.input = this.querySelector("input.k-input__control");
@@ -249,11 +253,11 @@ export class KashiCombobox extends HTMLBase {
249
253
  return `${header}<li id="${listId}-${i}" data-i="${i}" role="option" aria-selected="${on}"${o.disabled ? ' aria-disabled="true"' : ""}${i === this.active ? " data-active" : ""} class="k-option"><span class="k-option__body"><span>${mark(o.label)}</span>${o.description ? `<span class="k-option__desc">${esc(o.description)}</span>` : ""}</span>${on ? `<span class="k-option__check">${CHECK}</span>` : ""}</li>`;
250
254
  });
251
255
  if (!this.filtered.length)
252
- items.push(`<li class="k-listbox__empty" role="presentation">${esc(this.getAttribute("empty-message") ?? "نتیجه‌ای پیدا نشد.")}</li>`);
256
+ items.push(`<li class="k-listbox__empty" role="presentation">${esc(this.getAttribute("empty-message") ?? msg().common.noResults)}</li>`);
253
257
  if (this.filtered.length > MAX_RENDER)
254
- items.push(`<li class="k-listbox__empty" role="presentation">${toFaDigits(this.filtered.length - MAX_RENDER)} گزینهٔ دیگر؛ برای پیدا کردن تایپ کنید.</li>`);
258
+ items.push(`<li class="k-listbox__empty" role="presentation">${esc(msg().combobox.more(num(this, this.filtered.length - MAX_RENDER)))}</li>`);
255
259
  this.listbox.innerHTML = items.join("");
256
- this.live.textContent = this.typing ? `${toFaDigits(this.filtered.length)} نتیجه` : "";
260
+ this.live.textContent = this.typing ? msg().common.results(num(this, this.filtered.length)) : "";
257
261
  this.setActive(this.active);
258
262
  }
259
263
  setActive(i) {
@@ -319,14 +323,15 @@ export class KashiCombobox extends HTMLBase {
319
323
  KashiCombobox.observedAttributes = ["label", "hint", "error", "placeholder", "required", "disabled", "name", "value", "empty-message", "aria-label"];
320
324
  /** انتخاب یکی از ۳۱ استان با جستجو؛ با نام مرکز استان هم پیدا می‌شود (مثلاً «مشهد») */
321
325
  export class KashiProvinceSelect extends KashiCombobox {
322
- constructor() {
323
- super(...arguments);
324
- this.defaultLabel = "استان";
325
- this.defaultPlaceholder = "جستجوی استان یا شهر";
326
+ get defaultLabel() {
327
+ return msg().province.label;
328
+ }
329
+ get defaultPlaceholder() {
330
+ return msg().province.search;
326
331
  }
327
332
  source() {
328
333
  const capital = this.getAttribute("show-capital") !== "false";
329
- return PROVINCES.map((p) => ({ value: p.name, label: p.name, description: capital ? `مرکز: ${p.capital}` : undefined, keywords: [p.capital] }));
334
+ return PROVINCES.map((p) => ({ value: p.name, label: p.name, description: capital ? msg().province.capital(p.capital) : undefined, keywords: [p.capital] }));
330
335
  }
331
336
  detail() {
332
337
  return { value: this.value, province: PROVINCES.find((p) => p.name === this.value) ?? null };
@@ -343,9 +348,14 @@ const loose = (s) => searchText(s).replace(/[\s‌]/g, "");
343
348
  export class KashiCitySelect extends KashiCombobox {
344
349
  constructor() {
345
350
  super(...arguments);
346
- this.defaultLabel = "شهر";
347
351
  this.custom = null;
348
352
  }
353
+ get defaultLabel() {
354
+ return msg().city.label;
355
+ }
356
+ get defaultPlaceholder() {
357
+ return this.placeholderText();
358
+ }
349
359
  get province() {
350
360
  return this.getAttribute("province") || null;
351
361
  }
@@ -367,7 +377,7 @@ export class KashiCitySelect extends KashiCombobox {
367
377
  let o = city ? (list.find((x) => x.label === city && (!p || x.value.startsWith(`${p}${SEP}`))) ?? null) : null;
368
378
  if (city && !o) {
369
379
  this.custom = city;
370
- o = { value: `custom${SEP}${city}`, label: city, description: "خارج از فهرست" };
380
+ o = { value: `custom${SEP}${city}`, label: city, description: msg().city.outside };
371
381
  }
372
382
  this.selected = o;
373
383
  this.query = o?.label ?? "";
@@ -403,11 +413,10 @@ export class KashiCitySelect extends KashiCombobox {
403
413
  }
404
414
  }
405
415
  connectedCallback() {
406
- this.defaultPlaceholder = this.placeholderText();
407
416
  super.connectedCallback();
408
417
  }
409
418
  placeholderText() {
410
- return this.province ? "جستجوی شهر" : "نام شهر، مثلاً کاشان";
419
+ return this.province ? msg().city.search : msg().city.hint;
411
420
  }
412
421
  source(query) {
413
422
  const p = this.province;
@@ -415,10 +424,10 @@ export class KashiCitySelect extends KashiCombobox {
415
424
  ? getCities(p).map((c) => ({ value: `${p}${SEP}${c}`, label: c }))
416
425
  : Object.entries(CITIES).flatMap(([prov, cities]) => cities.map((c) => ({ value: `${prov}${SEP}${c}`, label: c, description: prov })));
417
426
  if (this.custom)
418
- list.unshift({ value: `custom${SEP}${this.custom}`, label: this.custom, description: "خارج از فهرست" });
427
+ list.unshift({ value: `custom${SEP}${this.custom}`, label: this.custom, description: msg().city.outside });
419
428
  const q = query.trim();
420
429
  if (this.getAttribute("allow-custom") !== "false" && q && q !== this.custom && !list.some((o) => loose(o.label) === loose(q)))
421
- list.push({ value: `custom${SEP}${q}`, label: q, description: "شهر دیگر؛ همین نام ثبت شود" });
430
+ list.push({ value: `custom${SEP}${q}`, label: q, description: msg().city.custom });
422
431
  return list;
423
432
  }
424
433
  accepts(o, q) {
package/dist/controls.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { formatNumber, normalizePersian, onlyDigits, toEnDigits, toFaDigits } from "@kashi-ui/persian";
2
- import { HTMLBase, bool, define, emit, esc, fieldHTML, setMessages, uid } from "./base.js";
2
+ import { bool, define, digitsOf, emit, esc, fieldHTML, HTMLBase, msg, num, setMessages, uid } from "./base.js";
3
3
  const REMOVE = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="m6 6 8 8M14 6l-8 8"/></svg>';
4
4
  /** چند فیلد پنهان با یک name، برای ارسال چند مقدار در فرم HTML */
5
5
  function syncHiddenList(host, name, values) {
@@ -60,7 +60,7 @@ export class KashiTagInput extends HTMLBase {
60
60
  render() {
61
61
  const id = this.key;
62
62
  const ariaLabel = this.getAttribute("aria-label");
63
- const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : "برچسب‌ها";
63
+ const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : msg().tagInput.label;
64
64
  const control = `<div class="k-tag-input"><input id="${id}" class="k-tag-input__control"${this.hasAttribute("disabled") ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}></div><span class="k-sr-only" aria-live="polite"></span>`;
65
65
  this.querySelector(":scope > .k-field")?.remove();
66
66
  this.insertAdjacentHTML("afterbegin", fieldHTML({ id, label, control }));
@@ -107,7 +107,7 @@ export class KashiTagInput extends HTMLBase {
107
107
  const t = normalizePersian(r).trim();
108
108
  if (!t)
109
109
  continue;
110
- const err = next.length >= max ? `حداکثر ${toFaDigits(max)} مورد` : !dup && next.includes(t) ? "این مورد قبلاً اضافه شده است." : this.validate?.(t);
110
+ const err = next.length >= max ? msg().common.max(num(this, max)) : !dup && next.includes(t) ? msg().tagInput.duplicate : this.validate?.(t);
111
111
  if (err) {
112
112
  this.localError = err;
113
113
  this.commit(next, added);
@@ -115,7 +115,7 @@ export class KashiTagInput extends HTMLBase {
115
115
  return;
116
116
  }
117
117
  next = [...next, t];
118
- added = `${t} اضافه شد`;
118
+ added = msg().common.added(t);
119
119
  }
120
120
  this.localError = null;
121
121
  this.input.value = "";
@@ -123,7 +123,7 @@ export class KashiTagInput extends HTMLBase {
123
123
  }
124
124
  removeAt(i) {
125
125
  const t = this.tags[i];
126
- this.commit(this.tags.filter((_, j) => j !== i), `${t} حذف شد`);
126
+ this.commit(this.tags.filter((_, j) => j !== i), msg().common.removed(t));
127
127
  this.input.focus();
128
128
  }
129
129
  commit(next, announce) {
@@ -140,7 +140,7 @@ export class KashiTagInput extends HTMLBase {
140
140
  box.querySelectorAll(".k-badge").forEach((b) => b.remove());
141
141
  const tone = this.getAttribute("tone") ?? "brand";
142
142
  const off = this.hasAttribute("disabled");
143
- const badges = this.tags.map((t, i) => `<span class="k-badge${tone !== "neutral" ? ` k-badge--${esc(tone)}` : ""}${off ? "" : " k-badge--removable"}">${esc(t)}${off ? "" : `<button type="button" class="k-badge__remove" data-i="${i}" aria-label="حذف ${esc(t)}">${REMOVE}</button>`}</span>`).join("");
143
+ const badges = this.tags.map((t, i) => `<span class="k-badge${tone !== "neutral" ? ` k-badge--${esc(tone)}` : ""}${off ? "" : " k-badge--removable"}">${esc(t)}${off ? "" : `<button type="button" class="k-badge__remove" data-i="${i}" aria-label="${esc(msg().common.remove(t))}">${REMOVE}</button>`}</span>`).join("");
144
144
  this.input.insertAdjacentHTML("beforebegin", badges);
145
145
  this.input.placeholder = this.tags.length ? "" : (this.getAttribute("placeholder") ?? "");
146
146
  this.paintMessages();
@@ -205,15 +205,15 @@ export class KashiNumberInput extends HTMLBase {
205
205
  if (n === null)
206
206
  return "";
207
207
  const s = this.hasAttribute("thousands") ? formatNumber(n, { digits: "latin" }) : String(n);
208
- return this.getAttribute("digits") === "latin" ? s : toFaDigits(s).replace(/,/g, "٬");
208
+ return digitsOf(this) === "latin" ? s : toFaDigits(s).replace(/,/g, "٬");
209
209
  }
210
210
  render() {
211
211
  const id = this.key;
212
212
  const ariaLabel = this.getAttribute("aria-label");
213
- const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : "تعداد";
213
+ const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : msg().numberInput.label;
214
214
  const disabled = this.hasAttribute("disabled");
215
215
  const unit = this.getAttribute("unit");
216
- const control = `<div class="k-number"><div class="k-input"${disabled ? ' data-state="disabled"' : ""}><button type="button" class="k-number__step" data-step="-1" tabindex="-1" aria-label="کاهش"${disabled ? " disabled" : ""}>${MINUS}</button><input id="${id}" class="k-input__control" type="text" inputmode="decimal" role="spinbutton" dir="ltr"${disabled ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}>${unit ? `<span class="k-input__addon">${esc(unit)}</span>` : ""}<button type="button" class="k-number__step" data-step="1" tabindex="-1" aria-label="افزایش"${disabled ? " disabled" : ""}>${PLUS}</button></div></div>`;
216
+ const control = `<div class="k-number"><div class="k-input"${disabled ? ' data-state="disabled"' : ""}><button type="button" class="k-number__step" data-step="-1" tabindex="-1" aria-label="${esc(msg().numberInput.decrement)}"${disabled ? " disabled" : ""}>${MINUS}</button><input id="${id}" class="k-input__control" type="text" inputmode="decimal" role="spinbutton" dir="ltr"${disabled ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}>${unit ? `<span class="k-input__addon">${esc(unit)}</span>` : ""}<button type="button" class="k-number__step" data-step="1" tabindex="-1" aria-label="${esc(msg().numberInput.increment)}"${disabled ? " disabled" : ""}>${PLUS}</button></div></div>`;
217
217
  this.querySelector(":scope > .k-field")?.remove();
218
218
  this.insertAdjacentHTML("afterbegin", fieldHTML({ id, label, control }));
219
219
  this.input = this.querySelector("input.k-input__control");
@@ -290,7 +290,6 @@ export class KashiNumberInput extends HTMLBase {
290
290
  }
291
291
  KashiNumberInput.observedAttributes = ["label", "hint", "error", "value", "min", "max", "step", "thousands", "digits", "unit", "disabled", "name", "aria-label"];
292
292
  const STAR = '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 2.8l2.8 5.9 6.4.8-4.7 4.4 1.2 6.3L12 17.1l-5.7 3.1 1.2-6.3L2.8 9.5l6.4-.8z"/><path class="k-rating__half" fill="none" d="M12 2.8v14.3l-5.7 3.1 1.2-6.3L2.8 9.5l6.4-.8z"/></svg>';
293
- const faNum = (n) => toFaDigits(String(n)).replace(".", "٫");
294
293
  /** امتیاز ستاره‌ای روی رادیوهای بومی؛ با readonly فقط نمایش (با نیم‌ستاره برای امتیاز اعشاری) */
295
294
  export class KashiRating extends HTMLBase {
296
295
  constructor() {
@@ -322,19 +321,20 @@ export class KashiRating extends HTMLBase {
322
321
  }
323
322
  render() {
324
323
  const max = Number(this.getAttribute("max")) || 5;
325
- const label = this.getAttribute("label") ?? "امتیاز";
324
+ const label = this.getAttribute("label") ?? msg().rating.label;
325
+ const faNum = (x) => num(this, x).replace(".", msg().common.decimal);
326
326
  const size = this.getAttribute("size");
327
327
  const cls = `k-rating${size === "sm" || size === "lg" ? ` k-rating--${size}` : ""}`;
328
328
  const c = this.current;
329
329
  const stars = Array.from({ length: max }, (_, i) => i + 1);
330
330
  const valueHtml = this.hasAttribute("show-value") ? `<span class="k-rating__value" aria-hidden="true">${faNum(c)}</span>` : "";
331
331
  if (this.hasAttribute("readonly")) {
332
- this.innerHTML = `<span class="${cls}" data-readonly="" role="img" aria-label="${esc(`${label}: ${faNum(c)} از ${faNum(max)}`)}">${stars.map((n) => `<span class="k-rating__star"${n <= Math.floor(c) ? ' data-filled=""' : ""}${n === Math.ceil(c) && c % 1 >= 0.25 ? ' data-half=""' : ""}>${STAR}</span>`).join("")}${valueHtml}</span>`;
332
+ this.innerHTML = `<span class="${cls}" data-readonly="" role="img" aria-label="${esc(`${label}: ${msg().common.of(faNum(c), faNum(max))}`)}">${stars.map((n) => `<span class="k-rating__star"${n <= Math.floor(c) ? ' data-filled=""' : ""}${n === Math.ceil(c) && c % 1 >= 0.25 ? ' data-half=""' : ""}>${STAR}</span>`).join("")}${valueHtml}</span>`;
333
333
  return;
334
334
  }
335
335
  const name = this.getAttribute("name") ?? this.key;
336
336
  const off = this.hasAttribute("disabled");
337
- this.innerHTML = `<fieldset class="${cls}"><legend class="k-sr-only">${esc(label)}</legend>${stars.map((n) => `<label class="k-rating__star"${n <= c ? ' data-filled=""' : ""}><input type="radio" class="k-sr-only" name="${esc(name)}" value="${n}"${c === n ? " checked" : ""}${off ? " disabled" : ""} aria-label="${faNum(n)} از ${faNum(max)}">${STAR}</label>`).join("")}${valueHtml}</fieldset>`;
337
+ this.innerHTML = `<fieldset class="${cls}"><legend class="k-sr-only">${esc(label)}</legend>${stars.map((n) => `<label class="k-rating__star"${n <= c ? ' data-filled=""' : ""}><input type="radio" class="k-sr-only" name="${esc(name)}" value="${n}"${c === n ? " checked" : ""}${off ? " disabled" : ""} aria-label="${esc(msg().common.of(faNum(n), faNum(max)))}">${STAR}</label>`).join("")}${valueHtml}</fieldset>`;
338
338
  const fs = this.querySelector("fieldset");
339
339
  const fill = (upto) => fs.querySelectorAll(".k-rating__star").forEach((s, i) => s.toggleAttribute("data-filled", i + 1 <= upto));
340
340
  fs.addEventListener("change", (e) => {
@@ -390,14 +390,14 @@ export class KashiPagination extends HTMLBase {
390
390
  }
391
391
  render() {
392
392
  const page = Math.min(this.page, this.total), total = this.total;
393
- const n = (v) => (this.getAttribute("digits") === "latin" ? String(v) : toFaDigits(v));
393
+ const n = (v) => (digitsOf(this) === "latin" ? String(v) : toFaDigits(v));
394
394
  const href = this.getAttribute("href");
395
395
  const focused = this.contains(document.activeElement) ? document.activeElement.dataset.to : null;
396
396
  const item = (to, body, cls, { current = false, disabled = false, aria = "" } = {}) => href && !disabled
397
397
  ? `<a class="${cls}" href="${esc(href.replace("{page}", String(to)))}" data-to="${to}"${current ? ' aria-current="page"' : ""}${aria ? ` aria-label="${esc(aria)}"` : ""}>${body}</a>`
398
398
  : `<button type="button" class="${cls}" data-to="${to}"${disabled ? " disabled" : ""}${current ? ' aria-current="page"' : ""}${aria ? ` aria-label="${esc(aria)}"` : ""}>${body}</button>`;
399
399
  const summary = this.getAttribute("summary");
400
- this.innerHTML = `<nav class="k-pagination" aria-label="${esc(this.getAttribute("label") ?? "صفحه‌بندی")}">${item(page - 1, `${chevron("m12 5-5 5 5 5")}قبلی`, "k-btn k-btn--secondary k-btn--sm", { disabled: page <= 1 })}${summary ? `<span class="k-pagination__summary">${esc(summary)}</span>` : ""}<ul class="k-pagination__pages" role="list">${pageRange(page, total, Number(this.getAttribute("siblings") ?? 1)).map((p) => `<li>${p === null ? '<span class="k-pagination__ellipsis" aria-hidden="true">…</span>' : item(p, n(p), "k-pagination__page", { current: p === page, aria: `صفحهٔ ${n(p)}` })}</li>`).join("")}</ul>${item(page + 1, `بعدی${chevron("m8 5 5 5-5 5")}`, "k-btn k-btn--secondary k-btn--sm", { disabled: page >= total })}</nav>`;
400
+ this.innerHTML = `<nav class="k-pagination" aria-label="${esc(this.getAttribute("label") ?? msg().pagination.nav)}">${item(page - 1, `${chevron("m12 5-5 5 5 5")}${esc(msg().common.prev)}`, "k-btn k-btn--secondary k-btn--sm", { disabled: page <= 1 })}${summary ? `<span class="k-pagination__summary">${esc(summary)}</span>` : ""}<ul class="k-pagination__pages" role="list">${pageRange(page, total, Number(this.getAttribute("siblings") ?? 1)).map((p) => `<li>${p === null ? '<span class="k-pagination__ellipsis" aria-hidden="true">…</span>' : item(p, n(p), "k-pagination__page", { current: p === page, aria: msg().pagination.page(n(p)) })}</li>`).join("")}</ul>${item(page + 1, `${esc(msg().common.next)}${chevron("m8 5 5 5-5 5")}`, "k-btn k-btn--secondary k-btn--sm", { disabled: page >= total })}</nav>`;
401
401
  this.querySelector("nav").addEventListener("click", (e) => {
402
402
  const b = e.target.closest("button[data-to]");
403
403
  if (!b || b.disabled)
package/dist/display.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { formatJalali, formatMoney, formatRelative, fromISODate, numberToWords, rialToToman, toFaDigits, tomanToRial } from "@kashi-ui/persian";
2
- import { HTMLBase, define, emit, esc } from "./base.js";
2
+ import { define, digitsOf, emit, esc, HTMLBase, lang, msg } from "./base.js";
3
3
  /** «2026-03-21» به نیمه‌شب محلی، و بقیهٔ قالب‌ها (با ساعت) با Date */
4
4
  const parseDateTime = (text) => {
5
5
  if (!text)
@@ -24,19 +24,21 @@ export class KashiMoney extends Display {
24
24
  const unit = this.getAttribute("unit") === "rial" ? "rial" : "toman";
25
25
  const from = this.getAttribute("from") === "rial" ? "rial" : this.getAttribute("from") === "toman" ? "toman" : unit;
26
26
  const compact = this.hasAttribute("compact");
27
- const digits = this.getAttribute("digits") === "latin" ? "latin" : "fa";
27
+ const digits = digitsOf(this);
28
28
  const signed = this.hasAttribute("signed");
29
29
  const size = this.getAttribute("size") ?? "md";
30
30
  const n = from === unit ? value : from === "rial" ? rialToToman(value) : tomanToRial(value);
31
- const amount = formatMoney(n, { unit, compact, digits, showUnit: false });
32
- const full = formatMoney(n, { unit, compact });
33
- const unitText = full.endsWith("همت") ? null : unit === "toman" ? "تومان" : "ریال";
31
+ const locale = lang(), M = msg();
32
+ const amount = formatMoney(n, { unit, compact, digits, locale, showUnit: false });
33
+ const full = formatMoney(n, { unit, compact, locale });
34
+ const unitName = unit === "toman" ? M.money.toman : M.money.rial;
35
+ const unitText = full.endsWith("همت") ? null : unitName;
34
36
  const spoken = this.getAttribute("words") !== "false" && Number.isSafeInteger(Math.round(n))
35
- ? `${numberToWords(Math.round(n))} ${unit === "toman" ? "تومان" : "ریال"}`
37
+ ? `${numberToWords(Math.round(n), { locale })} ${unitName}`
36
38
  : null;
37
39
  const tone = signed ? (n > 0 ? "positive" : n < 0 ? "negative" : null) : null;
38
40
  const hide = spoken ? ' aria-hidden="true"' : "";
39
- this.innerHTML = `<data value="${n}" class="k-money k-money--${esc(size)}"${tone ? ` data-tone="${tone}"` : ""}${spoken ? ` title="${esc(spoken)}"` : ""}><span class="k-money__amount"${hide}>${signed && n > 0 ? "+" : ""}${esc(amount)}</span>${unitText ? `<span class="k-money__unit"${hide}>${unitText}</span>` : ""}${spoken ? `<span class="k-sr-only">${n < 0 ? "" : signed && n > 0 ? "مثبت " : ""}${esc(spoken)}</span>` : ""}</data>`;
41
+ this.innerHTML = `<data value="${n}" class="k-money k-money--${esc(size)}"${tone ? ` data-tone="${tone}"` : ""}${spoken ? ` title="${esc(spoken)}"` : ""}><span class="k-money__amount"${hide}>${signed && n > 0 ? "+" : ""}${esc(amount)}</span>${unitText ? `<span class="k-money__unit"${hide}>${unitText}</span>` : ""}${spoken ? `<span class="k-sr-only">${n < 0 ? "" : signed && n > 0 ? M.money.positive : ""}${esc(spoken)}</span>` : ""}</data>`;
40
42
  }
41
43
  }
42
44
  KashiMoney.observedAttributes = ["value", "unit", "from", "compact", "digits", "signed", "words", "size"];
@@ -50,8 +52,8 @@ export class KashiDate extends Display {
50
52
  }
51
53
  const f = this.getAttribute("format");
52
54
  const style = f === "numeric" || f === "full" ? f : "long";
53
- const digits = this.getAttribute("digits") === "latin" ? "latin" : "fa";
54
- this.innerHTML = `<time datetime="${esc(this.getAttribute("datetime"))}">${esc(formatJalali(d, { style, digits }))}</time>`;
55
+ const digits = digitsOf(this);
56
+ this.innerHTML = `<time datetime="${esc(this.getAttribute("datetime"))}">${esc(formatJalali(d, { style, digits, locale: lang() }))}</time>`;
55
57
  }
56
58
  }
57
59
  KashiDate.observedAttributes = ["datetime", "format", "digits"];
@@ -71,8 +73,8 @@ export class KashiRelativeTime extends Display {
71
73
  this.textContent = "";
72
74
  return;
73
75
  }
74
- const digits = this.getAttribute("digits") === "latin" ? "latin" : "fa";
75
- this.innerHTML = `<time datetime="${d.toISOString()}" title="${esc(formatJalali(d, { style: "full", digits }))}">${esc(formatRelative(d, { digits }))}</time>`;
76
+ const digits = digitsOf(this);
77
+ this.innerHTML = `<time datetime="${d.toISOString()}" title="${esc(formatJalali(d, { style: "full", digits, locale: lang() }))}">${esc(formatRelative(d, { digits, locale: lang() }))}</time>`;
76
78
  }
77
79
  }
78
80
  KashiRelativeTime.observedAttributes = ["datetime", "digits", "refresh"];
@@ -114,14 +116,15 @@ export class KashiCountdown extends HTMLBase {
114
116
  render() {
115
117
  const left = this.remaining;
116
118
  const d = Math.floor(left / 86400), h = Math.floor((left % 86400) / 3600), m = Math.floor((left % 3600) / 60), s = left % 60;
117
- const fa = this.getAttribute("digits") !== "latin";
119
+ const fa = digitsOf(this) === "fa";
118
120
  const f = (x) => (fa ? toFaDigits(String(x)) : String(x));
119
- const spoken = [d && `${f(d)} روز`, h && `${f(h)} ساعت`, m && `${f(m)} دقیقه`, `${f(s)} ثانیه`].filter(Boolean).join(" و ");
121
+ const C = msg().countdown;
122
+ const spoken = [d && `${f(d)} ${C.days}`, h && `${f(h)} ${C.hours}`, m && `${f(m)} ${C.minutes}`, `${f(s)} ${C.seconds}`].filter(Boolean).join(msg().common.and);
120
123
  const label = this.getAttribute("label");
121
124
  const aria = esc(label ? `${label}: ${spoken}` : spoken);
122
125
  const doneAttr = left === 0 ? ' data-done=""' : "";
123
126
  if (this.getAttribute("variant") === "blocks") {
124
- const units = [...(d ? [[d, "روز"]] : []), [h, "ساعت"], [m, "دقیقه"], [s, "ثانیه"]];
127
+ const units = [...(d ? [[d, C.days]] : []), [h, C.hours], [m, C.minutes], [s, C.seconds]];
125
128
  this.innerHTML = `<div role="timer" aria-label="${aria}" class="k-countdown k-countdown--blocks"${doneAttr}>${units.map(([v, name]) => `<span class="k-countdown__unit" aria-hidden="true"><span class="k-countdown__num">${f(pad(v))}</span><span class="k-countdown__name">${name}</span></span>`).join("")}</div>`;
126
129
  }
127
130
  else {
package/dist/fields.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { onlyDigits, toFaDigits } from "@kashi-ui/persian";
2
- import { HTMLBase, define, emit, esc, fieldHTML, setMessages, syncHidden, uid } from "./base.js";
2
+ import { define, digitsOf, emit, esc, fieldHTML, HTMLBase, msg, setMessages, syncHidden, uid } from "./base.js";
3
3
  /**
4
4
  * کد تأیید یک‌بارمصرف: یک input واقعی (نه شش input جدا)، پس چسباندن، پاک کردن،
5
5
  * صفحه‌خوان و پر شدن خودکار کد پیامکی (autocomplete="one-time-code") کار می‌کنند.
@@ -49,7 +49,7 @@ export class KashiOtp extends HTMLBase {
49
49
  const length = this.length;
50
50
  const group = Number(this.getAttribute("group-after")) || 0;
51
51
  const ariaLabel = this.getAttribute("aria-label");
52
- const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : "کد تأیید";
52
+ const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : msg().otp.label;
53
53
  const slots = Array.from({ length }, (_, i) => `${group && i === group ? '<span class="k-otp__sep" aria-hidden="true"></span>' : ""}<span class="k-otp__slot" aria-hidden="true"></span>`).join("");
54
54
  const control = `<div class="k-otp${this.getAttribute("size") === "sm" ? " k-otp--sm" : ""}"><input id="${id}" class="k-otp__input" type="text" inputmode="numeric" autocomplete="one-time-code" pattern="\\d*" maxlength="${length * 2}"${this.hasAttribute("disabled") ? " disabled" : ""}${!label && ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ""}>${slots}</div>`;
55
55
  this.querySelector(":scope > .k-field")?.remove();
@@ -88,7 +88,7 @@ export class KashiOtp extends HTMLBase {
88
88
  }
89
89
  /** فقط خانه‌ها؛ مقدار input را دست نمی‌زند تا تایپ کاربر پیش از رویداد input پاک نشود */
90
90
  paintSlots() {
91
- const fa = this.getAttribute("digits") !== "latin";
91
+ const fa = digitsOf(this) === "fa";
92
92
  const focused = this.ownerDocument.activeElement === this.input;
93
93
  const active = Math.min(this.caret, this.length - 1);
94
94
  this.querySelectorAll(".k-otp__slot").forEach((slot, i) => {
package/dist/forms.d.ts CHANGED
@@ -93,7 +93,7 @@ declare abstract class TextField extends HTMLBase {
93
93
  protected valueSet: boolean;
94
94
  protected text: string;
95
95
  protected abstract multiline: boolean;
96
- protected abstract defaultLabel: string;
96
+ protected abstract get defaultLabel(): string;
97
97
  protected abstract hint(): string | null;
98
98
  connectedCallback(): void;
99
99
  attributeChangedCallback(name: string, old: string | null, next: string | null): void;
@@ -109,7 +109,7 @@ declare abstract class TextField extends HTMLBase {
109
109
  export declare class KashiPersianTextarea extends TextField {
110
110
  static observedAttributes: string[];
111
111
  protected multiline: boolean;
112
- protected defaultLabel: string;
112
+ protected get defaultLabel(): string;
113
113
  protected render(): void;
114
114
  private result;
115
115
  protected hint(): string | null;
@@ -118,7 +118,7 @@ export declare class KashiPersianTextarea extends TextField {
118
118
  export declare class KashiKeyboardFix extends TextField {
119
119
  static observedAttributes: string[];
120
120
  protected multiline: boolean;
121
- protected defaultLabel: string;
121
+ protected get defaultLabel(): string;
122
122
  protected render(): void;
123
123
  protected hint(): string | null;
124
124
  }