@kashi-ui/elements 0.25.0 → 1.0.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 (70) hide show
  1. package/README.md +11 -1
  2. package/dist/appointment.d.ts +40 -0
  3. package/dist/appointment.js +175 -0
  4. package/dist/base.d.ts +408 -0
  5. package/dist/base.js +31 -0
  6. package/dist/basic.d.ts +43 -0
  7. package/dist/basic.js +222 -0
  8. package/dist/calendar.js +21 -20
  9. package/dist/cdn/appointment-CrIZi8Bp.js +1 -0
  10. package/dist/cdn/base-DTQ50VdG.js +1 -0
  11. package/dist/cdn/basic-CrEW2RKF.js +1 -0
  12. package/dist/cdn/calendar-CYRrdGIf.js +16 -0
  13. package/dist/cdn/charts-D6KM26Vp.js +1 -0
  14. package/dist/cdn/combobox-6EFI2qqN.js +1 -0
  15. package/dist/cdn/controls-BwebEIlf.js +1 -0
  16. package/dist/cdn/display-DFuMkcj5.js +1 -0
  17. package/dist/cdn/fields-DhLIhHQ8.js +1 -0
  18. package/dist/cdn/forms-BHivKDdC.js +1 -0
  19. package/dist/cdn/holidays-DgbIAdlt.js +1 -0
  20. package/dist/cdn/inputs-R7nEHfbV.js +1 -0
  21. package/dist/cdn/kashi-elements.js +1 -1
  22. package/dist/cdn/map-H3xiZd8Y.js +12 -0
  23. package/dist/cdn/{menu-DhrqyLgy.js → menu-BAUSubXk.js} +1 -1
  24. package/dist/cdn/nav-NK-v48B5.js +1 -0
  25. package/dist/cdn/overlay-CBv8grz3.js +1 -0
  26. package/dist/cdn/pickers-BHzNt_X8.js +1 -0
  27. package/dist/cdn/table-Bt9WVLoo.js +1 -0
  28. package/dist/cdn/text-D4a_hlwL.js +1 -0
  29. package/dist/cdn/time-DJ3p1wvd.js +1 -0
  30. package/dist/cdn/words-BdADG_9U.js +1 -0
  31. package/dist/cdn.d.ts +1 -0
  32. package/dist/cdn.js +4 -0
  33. package/dist/charts.js +27 -24
  34. package/dist/combobox.d.ts +6 -5
  35. package/dist/combobox.js +29 -20
  36. package/dist/controls.js +15 -15
  37. package/dist/display.js +17 -14
  38. package/dist/fields.js +3 -3
  39. package/dist/forms.d.ts +3 -3
  40. package/dist/forms.js +54 -47
  41. package/dist/index.d.ts +12 -2
  42. package/dist/index.js +7 -1
  43. package/dist/inputs.d.ts +3 -3
  44. package/dist/inputs.js +31 -30
  45. package/dist/map.js +16 -13
  46. package/dist/nav.js +18 -16
  47. package/dist/overlay.js +5 -5
  48. package/dist/pickers.d.ts +1 -1
  49. package/dist/pickers.js +39 -38
  50. package/dist/table.js +7 -7
  51. package/dist/toast.js +3 -3
  52. package/package.json +4 -4
  53. package/dist/cdn/base-Cmq7zeIG.js +0 -1
  54. package/dist/cdn/calendar-Dm3fq2bs.js +0 -16
  55. package/dist/cdn/charts-DNOWi4sJ.js +0 -1
  56. package/dist/cdn/combobox-ZGrZ5okg.js +0 -1
  57. package/dist/cdn/controls-K-OZcjg4.js +0 -1
  58. package/dist/cdn/digits-DE1YGT94.js +0 -1
  59. package/dist/cdn/display-C975tS7j.js +0 -1
  60. package/dist/cdn/fields-BUTaSN8m.js +0 -1
  61. package/dist/cdn/forms-C-effPKn.js +0 -1
  62. package/dist/cdn/inputs-BjOddAM7.js +0 -1
  63. package/dist/cdn/jalali-BiM5Kfep.js +0 -1
  64. package/dist/cdn/map-BHuusa4t.js +0 -12
  65. package/dist/cdn/nav-D_w5BV_K.js +0 -1
  66. package/dist/cdn/overlay-DX9un2xm.js +0 -1
  67. package/dist/cdn/pickers-BFLpUruK.js +0 -1
  68. package/dist/cdn/table-DPQfDMs-.js +0 -1
  69. package/dist/cdn/text-D1U32D6K.js +0 -1
  70. package/dist/cdn/words-9OdzG7y6.js +0 -1
package/dist/inputs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { formatCardNumber, formatIban, formatLandline, formatMobile, formatNumber, formatPostalCode, getBankByCard, getBankByIban, getLandlineProvince, getMobileOperator, isValidCardNumber, isValidIban, isValidLandline, isValidLegalId, isValidMobile, isValidNationalId, isValidPostalCode, normalizeLandline, normalizeMobile, numberToWords, onlyDigits, toFaDigits, } from "@kashi-ui/persian";
2
- import { HTMLBase, bool, define, emit, esc, fieldHTML, setMessages, syncHidden, uid } from "./base.js";
1
+ import { formatCardNumber, formatIban, formatLandline, formatMobile, formatNumber, formatPostalCode, bankName, getBankByCard, getBankByIban, getLandlineProvince, getMobileOperator, operatorName, isValidCardNumber, isValidIban, isValidLandline, isValidLegalId, isValidMobile, isValidNationalId, isValidPostalCode, normalizeLandline, normalizeMobile, numberToWords, onlyDigits, toFaDigits, } from "@kashi-ui/persian";
2
+ import { bool, define, emit, esc, fieldHTML, HTMLBase, lang, msg, setMessages, syncHidden, uid } from "./base.js";
3
3
  const fa = (s) => toFaDigits(s);
4
4
  /** موقعیت مکان‌نما پس از n رقم در متن قالب‌بندی‌شده */
5
5
  function caretAfter(text, n) {
@@ -69,7 +69,7 @@ export class KashiDigitsInput extends HTMLBase {
69
69
  }
70
70
  get digits() {
71
71
  const d = this.getAttribute("digits");
72
- return d === "fa" || d === "latin" ? d : this.kind.digits;
72
+ return d === "fa" || d === "latin" ? d : lang() === "en" ? "latin" : this.kind.digits;
73
73
  }
74
74
  shown() {
75
75
  const f = this.kind.format(this.raw);
@@ -79,7 +79,7 @@ export class KashiDigitsInput extends HTMLBase {
79
79
  const k = this.kind;
80
80
  const id = this.key;
81
81
  const ariaLabel = this.getAttribute("aria-label");
82
- const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : k.label;
82
+ const label = this.hasAttribute("label") ? this.getAttribute("label") : ariaLabel ? null : k.label();
83
83
  const size = this.getAttribute("size");
84
84
  const required = this.hasAttribute("required");
85
85
  const disabled = this.hasAttribute("disabled");
@@ -119,7 +119,7 @@ export class KashiDigitsInput extends HTMLBase {
119
119
  /** پیام خطای فعلی؛ فیلد مبلغ آن را برای محدوده بازنویسی می‌کند */
120
120
  invalidText(touched) {
121
121
  const show = this.raw.length > 0 && (touched || this.raw.length === this.kind.maxDigits) && !this.isValid();
122
- return show ? (this.getAttribute("invalid-message") ?? this.kind.invalidMessage) : null;
122
+ return show ? (this.getAttribute("invalid-message") ?? this.kind.invalidMessage()) : null;
123
123
  }
124
124
  hintText() {
125
125
  return this.getAttribute("hint");
@@ -174,11 +174,11 @@ export class KashiNationalId extends KashiDigitsInput {
174
174
  constructor() {
175
175
  super(...arguments);
176
176
  this.kind = {
177
- label: "کد ملی",
177
+ label: () => msg().persianInput["national-id"],
178
178
  maxDigits: 10,
179
179
  format: (d) => d,
180
180
  valid: (d) => isValidNationalId(d),
181
- invalidMessage: "کد ملی معتبر نیست.",
181
+ invalidMessage: () => msg().invalid.nationalId,
182
182
  placeholder: { fa: "۰۰۰۰۰۰۰۰۰۰", latin: "0000000000" },
183
183
  digits: "fa",
184
184
  };
@@ -189,11 +189,11 @@ export class KashiLegalId extends KashiDigitsInput {
189
189
  constructor() {
190
190
  super(...arguments);
191
191
  this.kind = {
192
- label: "شناسهٔ ملی شرکت",
192
+ label: () => msg().persianInput["legal-id"],
193
193
  maxDigits: 11,
194
194
  format: (d) => d,
195
195
  valid: (d) => isValidLegalId(d),
196
- invalidMessage: "شناسهٔ ملی معتبر نیست.",
196
+ invalidMessage: () => msg().invalid.legalId,
197
197
  placeholder: { fa: "۰۰۰۰۰۰۰۰۰۰۰", latin: "00000000000" },
198
198
  digits: "fa",
199
199
  };
@@ -204,12 +204,12 @@ export class KashiMobile extends KashiDigitsInput {
204
204
  constructor() {
205
205
  super(...arguments);
206
206
  this.kind = {
207
- label: "شمارهٔ موبایل",
207
+ label: () => msg().persianInput.mobile,
208
208
  maxDigits: 11,
209
209
  normalize: normalizeMobile,
210
210
  format: formatMobile,
211
211
  valid: (d) => isValidMobile(d),
212
- invalidMessage: "شماره موبایل معتبر نیست.",
212
+ invalidMessage: () => msg().invalid.mobile,
213
213
  placeholder: { fa: "۰۹۱۲ ۳۴۵ ۶۷۸۹", latin: "0912 345 6789" },
214
214
  digits: "fa",
215
215
  type: "tel",
@@ -217,7 +217,7 @@ export class KashiMobile extends KashiDigitsInput {
217
217
  autocomplete: "tel-national",
218
218
  meta: (d) => {
219
219
  const op = getMobileOperator(d);
220
- return op ? { text: op.name } : null;
220
+ return op ? { text: operatorName(op, lang()) } : null;
221
221
  },
222
222
  detail: (d) => ({ operator: getMobileOperator(d)?.name ?? null }),
223
223
  };
@@ -228,12 +228,12 @@ export class KashiLandline extends KashiDigitsInput {
228
228
  constructor() {
229
229
  super(...arguments);
230
230
  this.kind = {
231
- label: "تلفن ثابت",
231
+ label: () => msg().persianInput.landline,
232
232
  maxDigits: 11,
233
233
  normalize: normalizeLandline,
234
234
  format: formatLandline,
235
235
  valid: (d) => isValidLandline(d),
236
- invalidMessage: "شمارهٔ تلفن ثابت معتبر نیست.",
236
+ invalidMessage: () => msg().invalid.landline,
237
237
  placeholder: { fa: "۰۲۱ ۸۸۷۷ ۶۶۵۵", latin: "021 8877 6655" },
238
238
  digits: "fa",
239
239
  type: "tel",
@@ -252,17 +252,17 @@ export class KashiCardNumber extends KashiDigitsInput {
252
252
  constructor() {
253
253
  super(...arguments);
254
254
  this.kind = {
255
- label: "شمارهٔ کارت",
255
+ label: () => msg().persianInput.card,
256
256
  maxDigits: 16,
257
257
  format: formatCardNumber,
258
258
  valid: (d) => isValidCardNumber(d),
259
- invalidMessage: "شماره کارت معتبر نیست.",
259
+ invalidMessage: () => msg().invalid.card,
260
260
  placeholder: { fa: "۰۰۰۰ ۰۰۰۰ ۰۰۰۰ ۰۰۰۰", latin: "0000 0000 0000 0000" },
261
261
  digits: "fa",
262
262
  autocomplete: "cc-number",
263
263
  meta: (d) => {
264
264
  const bank = getBankByCard(d);
265
- return bank ? { text: bank.name, bank } : null;
265
+ return bank ? { text: bankName(bank, lang()), bank } : null;
266
266
  },
267
267
  detail: (d) => ({ bank: getBankByCard(d)?.name ?? null }),
268
268
  };
@@ -273,16 +273,16 @@ export class KashiIban extends KashiDigitsInput {
273
273
  constructor() {
274
274
  super(...arguments);
275
275
  this.kind = {
276
- label: "شمارهٔ شبا",
276
+ label: () => msg().persianInput.iban,
277
277
  maxDigits: 24,
278
278
  format: (d) => formatIban(d),
279
279
  valid: (d) => isValidIban(d),
280
- invalidMessage: "شماره شبا معتبر نیست.",
280
+ invalidMessage: () => msg().invalid.iban,
281
281
  placeholder: { fa: "IR00 0000 0000 0000 0000 0000 00", latin: "IR00 0000 0000 0000 0000 0000 00" },
282
282
  digits: "latin",
283
283
  meta: (d) => {
284
284
  const bank = getBankByIban(d);
285
- return bank ? { text: bank.name, bank } : null;
285
+ return bank ? { text: bankName(bank, lang()), bank } : null;
286
286
  },
287
287
  submit: (d) => (d ? `IR${d}` : ""),
288
288
  detail: (d) => ({ bank: getBankByIban(d)?.name ?? null }),
@@ -297,11 +297,11 @@ export class KashiPostalCode extends KashiDigitsInput {
297
297
  constructor() {
298
298
  super(...arguments);
299
299
  this.kind = {
300
- label: "کد پستی",
300
+ label: () => msg().persianInput.postal,
301
301
  maxDigits: 10,
302
302
  format: formatPostalCode,
303
303
  valid: (d) => isValidPostalCode(d),
304
- invalidMessage: "کد پستی معتبر نیست.",
304
+ invalidMessage: () => msg().invalid.postal,
305
305
  placeholder: { fa: "۱۲۳۴۵-۶۷۸۹۰", latin: "12345-67890" },
306
306
  digits: "fa",
307
307
  autocomplete: "postal-code",
@@ -313,16 +313,16 @@ export class KashiPrice extends KashiDigitsInput {
313
313
  constructor() {
314
314
  super(...arguments);
315
315
  this.kind = {
316
- label: "مبلغ",
316
+ label: () => msg().persianInput.price,
317
317
  maxDigits: 15,
318
318
  normalize: (d) => d.replace(/^0+(?=\d)/, ""),
319
- format: (d) => (d ? formatNumber(d, { digits: "latin" }).replace(/,/g, "٬") : ""),
319
+ format: (d) => (d ? formatNumber(d, { digits: "latin" }).replace(/,/g, this.digits === "fa" ? "٬" : ",") : ""),
320
320
  valid: () => this.inRange(),
321
- invalidMessage: "",
321
+ invalidMessage: () => "",
322
322
  placeholder: { fa: "۰", latin: "0" },
323
323
  digits: "fa",
324
324
  meta: (_d, host) => {
325
- const unit = host.getAttribute("unit") ?? "تومان";
325
+ const unit = host.getAttribute("unit") ?? msg().money.toman;
326
326
  return unit ? { text: unit } : null;
327
327
  },
328
328
  detail: (d) => ({ value: d ? Number(d) : null }),
@@ -349,13 +349,14 @@ export class KashiPrice extends KashiDigitsInput {
349
349
  if (custom)
350
350
  return custom;
351
351
  const min = this.num("min"), max = this.num("max");
352
- const f = (n) => fa(formatNumber(n));
353
- return `مبلغ باید ${min !== undefined ? `حداقل ${f(min)}` : ""}${min !== undefined && max !== undefined ? " و " : ""}${max !== undefined ? `حداکثر ${f(max)}` : ""} باشد.`;
352
+ const f = (n) => (this.digits === "fa" ? fa(formatNumber(n)) : formatNumber(n, { digits: "latin" }));
353
+ const I = msg().invalid;
354
+ return min !== undefined && max !== undefined ? I.priceRange(f(min), f(max)) : min !== undefined ? I.priceMin(f(min)) : I.priceMax(f(max));
354
355
  }
355
356
  hintText() {
356
357
  if (this.raw && bool(this, "show-words", true)) {
357
- const unit = this.getAttribute("unit") ?? "تومان";
358
- return `${numberToWords(Number(this.raw))}${unit ? ` ${unit}` : ""}`;
358
+ const unit = this.getAttribute("unit") ?? msg().money.toman;
359
+ return `${numberToWords(Number(this.raw), { locale: lang() })}${unit ? ` ${unit}` : ""}`;
359
360
  }
360
361
  return this.getAttribute("hint");
361
362
  }
package/dist/map.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EMPTY_ADDRESS, PROVINCES, formatNumber, getCities } from "@kashi-ui/persian";
2
- import { HTMLBase, afterParse, bool, define, emit, esc, uid } from "./base.js";
2
+ import { afterParse, bool, define, digitsOf, emit, esc, HTMLBase, msg, uid } from "./base.js";
3
3
  import { IRAN_PROVINCES, IRAN_VIEWBOX } from "./iranMapData.js";
4
4
  // فرم نشانی از این عنصرها ساخته شده؛ با import همین فایل به‌تنهایی هم ثبت شوند
5
5
  import "./combobox.js";
@@ -83,7 +83,7 @@ export class KashiIranMap extends HTMLBase {
83
83
  return this.multiple || bool(this, "selectable") || this.hasAttribute("value") || this.hasAttribute("name");
84
84
  }
85
85
  fmt(n) {
86
- return formatNumber(n);
86
+ return formatNumber(n, { digits: digitsOf(this) });
87
87
  }
88
88
  level(n) {
89
89
  const data = this.values;
@@ -115,7 +115,7 @@ export class KashiIranMap extends HTMLBase {
115
115
  return typeof v === "number" ? `${n}: ${this.fmt(v)}${unit ? ` ${unit}` : ""}` : n;
116
116
  }
117
117
  render() {
118
- const label = this.getAttribute("label") ?? "نقشهٔ استان‌های ایران";
118
+ const label = this.getAttribute("label") ?? msg().iranMap.label;
119
119
  const sel = this.selectable;
120
120
  const multi = this.multiple;
121
121
  const data = this.values;
@@ -133,7 +133,7 @@ export class KashiIranMap extends HTMLBase {
133
133
  ? `<table class="k-sr-only"><caption>${esc(label)}</caption><tbody>${NAMES.filter((n) => typeof data[n] === "number").sort((a, b) => data[b] - data[a]).map((n) => `<tr><th scope="row">${esc(n)}</th><td>${this.fmt(data[n])}${unit ? ` ${esc(unit)}` : ""}</td></tr>`).join("")}</tbody></table>`
134
134
  : "";
135
135
  // نسبت ابعاد با aspect-ratio، نه padding درصدی: در چیدمان flex ستونی ارتفاع padding درصدی صفر حساب می‌شود
136
- this.innerHTML = `<figure class="k-iran-map${sel ? " k-iran-map--selectable" : ""}"><div class="k-iran-map__canvas" style="block-size: auto; aspect-ratio: ${VW} / ${VH}"><svg viewBox="${IRAN_VIEWBOX}" class="k-iran-map__svg" role="${sel ? (multi ? "group" : "radiogroup") : "img"}" aria-label="${esc(sel ? label : `${label}${data ? "؛ جدول داده در ادامه" : ""}`)}">${paths}</svg><div class="k-chart__tip" aria-hidden="true" style="display: none"></div></div>${legend}${table}</figure>`;
136
+ this.innerHTML = `<figure class="k-iran-map${sel ? " k-iran-map--selectable" : ""}"><div class="k-iran-map__canvas" style="block-size: auto; aspect-ratio: ${VW} / ${VH}"><svg viewBox="${IRAN_VIEWBOX}" class="k-iran-map__svg" role="${sel ? (multi ? "group" : "radiogroup") : "img"}" aria-label="${esc(sel ? label : `${label}${data ? msg().iranMap.tableNote : ""}`)}">${paths}</svg><div class="k-chart__tip" aria-hidden="true" style="display: none"></div></div>${legend}${table}</figure>`;
137
137
  const svg = this.querySelector("svg");
138
138
  const fig = this.querySelector("figure");
139
139
  svg.addEventListener("pointerover", (e) => {
@@ -235,7 +235,7 @@ export class KashiIranMap extends HTMLBase {
235
235
  const w = this.querySelector("figure").clientWidth || VW;
236
236
  const v = this.values?.[n];
237
237
  const unit = this.getAttribute("unit");
238
- const row = typeof v === "number" ? `${this.fmt(v)}${unit ? ` ${unit}` : ""}` : `مرکز: ${CAPITAL[n]}`;
238
+ const row = typeof v === "number" ? `${this.fmt(v)}${unit ? ` ${unit}` : ""}` : msg().province.capital(CAPITAL[n]);
239
239
  box.innerHTML = `<span class="k-chart__tip-title">${esc(n)}</span><span class="k-chart__tip-row"><strong>${esc(row)}</strong></span>`;
240
240
  // داخل ظرف بماند و راهنمای استان‌های شمالی بالای نقشه بیرون نزند
241
241
  box.style.left = `${Math.min(Math.max((p.x / VW) * w, 80), w - 80)}px`;
@@ -244,7 +244,10 @@ export class KashiIranMap extends HTMLBase {
244
244
  }
245
245
  }
246
246
  KashiIranMap.observedAttributes = ["data", "value", "label", "unit", "steps", "scale", "legend", "selectable", "multiple", "name"];
247
- const LABELS = { province: "استان", city: "شهر", street: "نشانی", plaque: "پلاک", unit: "واحد", postalCode: "کد پستی" };
247
+ const LABELS = () => {
248
+ const A = msg().address;
249
+ return { province: A.province, city: A.city, street: A.street, plaque: A.plaque, unit: A.unit, postalCode: A.postalCode };
250
+ };
248
251
  /**
249
252
  * فرم نشانی ایرانی: استان، شهرِ همان استان، نشانی، پلاک، واحد و کد پستی ده‌رقمی؛ با map نقشهٔ ایران برای انتخاب استان.
250
253
  * با انتخاب شهر بدون استان، استان خودکار پر می‌شود و با عوض شدن استان، شهرِ نامربوط پاک می‌شود.
@@ -291,7 +294,7 @@ export class KashiAddressForm extends HTMLBase {
291
294
  }
292
295
  /** خطای هر فیلد، مثلاً از اعتبارسنجی سرور: { plaque: "پلاک را بنویسید." } */
293
296
  set errors(e) {
294
- for (const f of Object.keys(LABELS)) {
297
+ for (const f of Object.keys(LABELS())) {
295
298
  const el = this.querySelector(`[data-field="${f}"]`);
296
299
  if (!el)
297
300
  continue;
@@ -306,7 +309,7 @@ export class KashiAddressForm extends HTMLBase {
306
309
  }
307
310
  }
308
311
  label(f) {
309
- return this.getAttribute(`label-${f === "postalCode" ? "postal-code" : f}`) ?? LABELS[f];
312
+ return this.getAttribute(`label-${f === "postalCode" ? "postal-code" : f}`) ?? LABELS()[f];
310
313
  }
311
314
  render() {
312
315
  const a = this.address;
@@ -317,18 +320,18 @@ export class KashiAddressForm extends HTMLBase {
317
320
  const map = bool(this, "map");
318
321
  const k = this.key;
319
322
  const reqMark = req ? '<span class="k-label__required" aria-hidden="true">*</span>' : "";
320
- const legend = this.getAttribute("legend") ?? "نشانی";
323
+ const legend = this.getAttribute("legend") ?? msg().address.legend;
321
324
  this.innerHTML = `<fieldset class="k-address${map ? " k-address--map" : ""}"${this.hasAttribute("disabled") ? " disabled" : ""}>
322
325
  ${legend ? `<legend class="k-address__legend">${esc(legend)}</legend>` : ""}
323
326
  <div class="k-address__fields">
324
- <kashi-province-select class="k-address__province" data-field="province" label="${esc(this.label("province"))}" show-capital="false" placeholder="جستجوی استان"${n("province")}${r}></kashi-province-select>
327
+ <kashi-province-select class="k-address__province" data-field="province" label="${esc(this.label("province"))}" show-capital="false" placeholder="${esc(msg().address.provinceSearch)}"${n("province")}${r}></kashi-province-select>
325
328
  <kashi-city-select class="k-address__city" data-field="city" label="${esc(this.label("city"))}"${n("city")}${r}></kashi-city-select>
326
- <div class="k-field k-address__street" data-field="street"><div class="k-field__label-row"><label class="k-label" for="${k}-street">${esc(this.label("street"))}${reqMark}</label></div><div class="k-input k-input--textarea k-input--autoresize"><textarea id="${k}-street" class="k-input__control" rows="2" autocomplete="street-address" placeholder="خیابان، کوچه، نام ساختمان"${n("street")}${r}></textarea></div></div>
329
+ <div class="k-field k-address__street" data-field="street"><div class="k-field__label-row"><label class="k-label" for="${k}-street">${esc(this.label("street"))}${reqMark}</label></div><div class="k-input k-input--textarea k-input--autoresize"><textarea id="${k}-street" class="k-input__control" rows="2" autocomplete="street-address" placeholder="${esc(msg().address.streetHint)}"${n("street")}${r}></textarea></div></div>
327
330
  <div class="k-field k-address__plaque" data-field="plaque"><div class="k-field__label-row"><label class="k-label" for="${k}-plaque">${esc(this.label("plaque"))}${reqMark}</label></div><div class="k-input"><input id="${k}-plaque" class="k-input__control" inputmode="numeric" autocomplete="address-line2"${n("plaque")}${r}></div></div>
328
- <div class="k-field k-address__unit" data-field="unit"><div class="k-field__label-row"><label class="k-label" for="${k}-unit">${esc(this.label("unit"))}${req ? '<span class="k-label__optional">اختیاری</span>' : ""}</label></div><div class="k-input"><input id="${k}-unit" class="k-input__control" inputmode="numeric" autocomplete="address-line3"${n("unit")}></div></div>
331
+ <div class="k-field k-address__unit" data-field="unit"><div class="k-field__label-row"><label class="k-label" for="${k}-unit">${esc(this.label("unit"))}${req ? `<span class="k-label__optional">${esc(msg().common.optional)}</span>` : ""}</label></div><div class="k-input"><input id="${k}-unit" class="k-input__control" inputmode="numeric" autocomplete="address-line3"${n("unit")}></div></div>
329
332
  <kashi-postal-code class="k-address__postal" data-field="postalCode" label="${esc(this.label("postalCode"))}"${n("postalCode")}${r}></kashi-postal-code>
330
333
  </div>
331
- ${map ? `<kashi-iran-map class="k-address__map" selectable label="${esc(`${this.label("province")} روی نقشه`)}"></kashi-iran-map>` : ""}
334
+ ${map ? `<kashi-iran-map class="k-address__map" selectable label="${esc(msg().iranMap.onMap(this.label("province")))}"></kashi-iran-map>` : ""}
332
335
  </fieldset>`;
333
336
  this.built = true;
334
337
  const province = this.querySelector("kashi-province-select");
package/dist/nav.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizePersian, toFaDigits } from "@kashi-ui/persian";
2
- import { HTMLBase, afterParse, bool, define, emit, esc, uid } from "./base.js";
1
+ import { normalizePersian } from "@kashi-ui/persian";
2
+ import { afterParse, bool, define, emit, esc, HTMLBase, msg, num, uid } from "./base.js";
3
3
  const search = (s) => normalizePersian(s, { digits: "latin" }).toLowerCase();
4
4
  const items = (menu) => [...menu.querySelectorAll('[role^="menuitem"]:not([aria-disabled="true"])')];
5
5
  const SEARCH = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true"><circle cx="9" cy="9" r="5.5"/><path d="m13.5 13.5 3 3"/></svg>';
@@ -68,9 +68,10 @@ export class KashiCommandPalette extends HTMLBase {
68
68
  build() {
69
69
  const d = document.createElement("dialog");
70
70
  d.className = "k-dialog k-command";
71
- const title = this.getAttribute("label") ?? "پالت فرمان";
71
+ const M = msg();
72
+ const title = this.getAttribute("label") ?? M.command.title;
72
73
  d.setAttribute("aria-label", title);
73
- d.innerHTML = `<div class="k-input"><span class="k-input__addon">${SEARCH}</span><input class="k-input__control" type="text" role="combobox" aria-expanded="true" aria-controls="${this.key}-list" aria-autocomplete="list" aria-label="${esc(title)}" placeholder="${esc(this.getAttribute("placeholder") ?? "جستجوی دستور یا صفحه…")}" autocomplete="off"></div><ul id="${this.key}-list" role="listbox" aria-label="${esc(title)}" class="k-listbox k-listbox--inline k-command__list"></ul><div class="k-command__footer" aria-hidden="true"><span><kbd class="k-kbd">↑</kbd><kbd class="k-kbd">↓</kbd>حرکت</span><span><kbd class="k-kbd">Enter</kbd>انتخاب</span><span><kbd class="k-kbd">Esc</kbd>بستن</span></div><span class="k-sr-only" aria-live="polite"></span>`;
74
+ d.innerHTML = `<div class="k-input"><span class="k-input__addon">${SEARCH}</span><input class="k-input__control" type="text" role="combobox" aria-expanded="true" aria-controls="${this.key}-list" aria-autocomplete="list" aria-label="${esc(title)}" placeholder="${esc(this.getAttribute("placeholder") ?? M.command.placeholder)}" autocomplete="off"></div><ul id="${this.key}-list" role="listbox" aria-label="${esc(title)}" class="k-listbox k-listbox--inline k-command__list"></ul><div class="k-command__footer" aria-hidden="true"><span><kbd class="k-kbd">↑</kbd><kbd class="k-kbd">↓</kbd>${esc(M.command.navigate)}</span><span><kbd class="k-kbd">Enter</kbd>${esc(M.command.select)}</span><span><kbd class="k-kbd">Esc</kbd>${esc(M.common.close)}</span></div><span class="k-sr-only" aria-live="polite"></span>`;
74
75
  this.append(d);
75
76
  this.dialog = d;
76
77
  const input = d.querySelector("input");
@@ -152,14 +153,14 @@ export class KashiCommandPalette extends HTMLBase {
152
153
  last = it.group;
153
154
  return `${header}<li id="${this.key}-list-${i}" data-i="${i}" role="option" aria-selected="${i === this.active}"${i === this.active ? " data-active" : ""} class="k-option"><span class="k-option__body"><span>${mark(it.label)}</span>${it.description ? `<span class="k-option__desc">${esc(it.description)}</span>` : ""}</span>${it.shortcut ? `<span class="k-menu__shortcut" aria-hidden="true">${esc(it.shortcut)}</span>` : ""}</li>`;
154
155
  }).join("")
155
- : '<li role="presentation" class="k-listbox__empty">دستوری پیدا نشد.</li>';
156
+ : `<li role="presentation" class="k-listbox__empty">${esc(msg().command.empty)}</li>`;
156
157
  const input = d.querySelector("input");
157
158
  if (list.length)
158
159
  input.setAttribute("aria-activedescendant", `${this.key}-list-${this.active}`);
159
160
  else
160
161
  input.removeAttribute("aria-activedescendant");
161
162
  d.querySelector(`#${this.key}-list-${this.active}`)?.scrollIntoView?.({ block: "nearest" });
162
- d.querySelector(":scope > .k-sr-only").textContent = this.query ? `${toFaDigits(list.length)} نتیجه` : "";
163
+ d.querySelector(":scope > .k-sr-only").textContent = this.query ? msg().common.results(num(this, list.length)) : "";
163
164
  }
164
165
  }
165
166
  KashiCommandPalette.observedAttributes = ["placeholder", "label", "shortcut"];
@@ -192,7 +193,7 @@ export class KashiContextMenu extends HTMLBase {
192
193
  this.setAttribute("aria-describedby", `${this.key}-hint`);
193
194
  const menu = document.createElement("ul");
194
195
  menu.setAttribute("role", "menu");
195
- menu.setAttribute("aria-label", this.getAttribute("label") ?? "گزینه‌ها");
196
+ menu.setAttribute("aria-label", this.getAttribute("label") ?? msg().contextMenu.label);
196
197
  menu.className = "k-menu k-menu--context";
197
198
  menu.hidden = true;
198
199
  for (const el of [...(source?.children ?? [])]) {
@@ -221,7 +222,7 @@ export class KashiContextMenu extends HTMLBase {
221
222
  menu.append(li);
222
223
  }
223
224
  source?.remove();
224
- this.insertAdjacentHTML("beforeend", `<span id="${this.key}-hint" class="k-sr-only">برای گزینه‌ها راست‌کلیک کنید یا Shift+F10 را بزنید.</span>`);
225
+ this.insertAdjacentHTML("beforeend", `<span id="${this.key}-hint" class="k-sr-only">${esc(msg().contextMenu.hint)}</span>`);
225
226
  this.append(menu);
226
227
  this.menu = menu;
227
228
  this.addEventListener("contextmenu", (e) => {
@@ -333,11 +334,12 @@ export class KashiCarousel extends HTMLBase {
333
334
  return Math.max(1, this.slides.length - this.perView + 1);
334
335
  }
335
336
  build() {
336
- const label = this.getAttribute("label") ?? "اسلایدر";
337
+ const M = msg();
338
+ const label = this.getAttribute("label") ?? M.carousel.slider;
337
339
  const children = [...this.children];
338
340
  const section = document.createElement("section");
339
341
  section.className = "k-carousel";
340
- section.setAttribute("aria-roledescription", "اسلایدر");
342
+ section.setAttribute("aria-roledescription", M.carousel.slider);
341
343
  section.setAttribute("aria-label", label);
342
344
  section.style.setProperty("--k-carousel-per-view", String(this.perView));
343
345
  const track = document.createElement("div");
@@ -348,8 +350,8 @@ export class KashiCarousel extends HTMLBase {
348
350
  const s = document.createElement("div");
349
351
  s.className = "k-carousel__slide";
350
352
  s.setAttribute("role", "group");
351
- s.setAttribute("aria-roledescription", "اسلاید");
352
- s.setAttribute("aria-label", `${toFaDigits(i + 1)} از ${toFaDigits(children.length)}`);
353
+ s.setAttribute("aria-roledescription", M.carousel.slide);
354
+ s.setAttribute("aria-label", M.carousel.slideOf(num(this, i + 1), num(this, children.length)));
353
355
  s.id = `${this.key}-s${i}`;
354
356
  s.append(c);
355
357
  track.append(s);
@@ -359,7 +361,7 @@ export class KashiCarousel extends HTMLBase {
359
361
  this.track = track;
360
362
  if (this.pages > 1) {
361
363
  const dots = this.getAttribute("dots") !== "false";
362
- section.insertAdjacentHTML("beforeend", `<div class="k-carousel__bar">${dots ? `<ul class="k-carousel__dots" aria-label="اسلایدهای ${esc(label)}">${Array.from({ length: this.pages }, (_, i) => `<li><button type="button" class="k-carousel__dot" data-go="${i}" aria-label="${toFaDigits(i + 1)} از ${toFaDigits(this.pages)}" aria-controls="${this.key}-s${i}"></button></li>`).join("")}</ul>` : "<span></span>"}<div class="k-carousel__nav"><button type="button" class="k-btn k-btn--secondary k-btn--icon k-btn--sm" data-step="-1" aria-label="قبلی">${ARROW(false)}</button><button type="button" class="k-btn k-btn--secondary k-btn--icon k-btn--sm" data-step="1" aria-label="بعدی">${ARROW(true)}</button></div></div>`);
364
+ section.insertAdjacentHTML("beforeend", `<div class="k-carousel__bar">${dots ? `<ul class="k-carousel__dots" aria-label="${esc(M.carousel.slides(label))}">${Array.from({ length: this.pages }, (_, i) => `<li><button type="button" class="k-carousel__dot" data-go="${i}" aria-label="${esc(M.carousel.slideOf(num(this, i + 1), num(this, this.pages)))}" aria-controls="${this.key}-s${i}"></button></li>`).join("")}</ul>` : "<span></span>"}<div class="k-carousel__nav"><button type="button" class="k-btn k-btn--secondary k-btn--icon k-btn--sm" data-step="-1" aria-label="${esc(M.common.prev)}">${ARROW(false)}</button><button type="button" class="k-btn k-btn--secondary k-btn--icon k-btn--sm" data-step="1" aria-label="${esc(M.common.next)}">${ARROW(true)}</button></div></div>`);
363
365
  section.querySelector(".k-carousel__bar").addEventListener("click", (e) => {
364
366
  const b = e.target.closest("button");
365
367
  if (!b)
@@ -701,7 +703,7 @@ export class KashiTour extends HTMLBase {
701
703
  });
702
704
  document.body.append(this.card);
703
705
  }
704
- this.card.innerHTML = `<span class="k-tour__step">مرحلهٔ ${toFaDigits(this.i + 1)} از ${toFaDigits(n)}</span><p class="k-tour__title" id="${this.key}-t">${esc(s.title)}</p><p class="k-tour__body" id="${this.key}-b">${esc(s.content)}</p><div class="k-tour__actions"><button type="button" class="k-btn k-btn--link k-tour__skip" data-act="skip">رد کردن</button><span class="k-tour__progress" aria-hidden="true">${this.steps.map((_, k) => `<span${k <= this.i ? ' data-on=""' : ""}></span>`).join("")}</span>${this.i > 0 ? '<button type="button" class="k-btn k-btn--secondary k-btn--sm" data-act="prev">قبلی</button>' : ""}<button type="button" class="k-btn k-btn--primary k-btn--sm" data-act="${last ? "done" : "next"}">${last ? "تمام" : "بعدی"}</button></div>`;
706
+ this.card.innerHTML = `<span class="k-tour__step">${esc(msg().tour.step(num(this, this.i + 1), num(this, n)))}</span><p class="k-tour__title" id="${this.key}-t">${esc(s.title)}</p><p class="k-tour__body" id="${this.key}-b">${esc(s.content)}</p><div class="k-tour__actions"><button type="button" class="k-btn k-btn--link k-tour__skip" data-act="skip">${esc(msg().tour.skip)}</button><span class="k-tour__progress" aria-hidden="true">${this.steps.map((_, k) => `<span${k <= this.i ? ' data-on=""' : ""}></span>`).join("")}</span>${this.i > 0 ? '<button type="button" class="k-btn k-btn--secondary k-btn--sm" data-act="prev">${esc(msg().common.prev)}</button>' : ""}<button type="button" class="k-btn k-btn--primary k-btn--sm" data-act="${last ? "done" : "next"}">${esc(last ? msg().tour.done : msg().common.next)}</button></div>`;
705
707
  const el = document.querySelector(s.target);
706
708
  const reduce = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
707
709
  el?.scrollIntoView?.({ block: "center", behavior: reduce ? "auto" : "smooth" });
@@ -819,7 +821,7 @@ export class KashiSplitter extends HTMLBase {
819
821
  handle.setAttribute("aria-orientation", this.vertical ? "horizontal" : "vertical");
820
822
  handle.setAttribute("aria-valuemin", String(this.num("min", 15)));
821
823
  handle.setAttribute("aria-valuemax", String(this.num("max", 85)));
822
- handle.setAttribute("aria-label", this.getAttribute("label") ?? "تغییر اندازهٔ پنل‌ها");
824
+ handle.setAttribute("aria-label", this.getAttribute("label") ?? msg().splitter.label);
823
825
  this.paint();
824
826
  }
825
827
  set(p, fire = true) {
@@ -838,7 +840,7 @@ export class KashiSplitter extends HTMLBase {
838
840
  pane.style.blockSize = this.vertical ? `${this.current}%` : "";
839
841
  const handle = box.querySelector(".k-splitter__handle");
840
842
  handle.setAttribute("aria-valuenow", String(this.current));
841
- handle.setAttribute("aria-valuetext", `${toFaDigits(Math.round(this.current))}٪`);
843
+ handle.setAttribute("aria-valuetext", msg().common.percent(num(this, Math.round(this.current))));
842
844
  }
843
845
  }
844
846
  KashiSplitter.observedAttributes = ["size", "min", "max", "orientation", "label", "height"];
package/dist/overlay.js CHANGED
@@ -1,4 +1,4 @@
1
- import { HTMLBase, ICONS, afterParse, bool, define, emit, esc, uid } from "./base.js";
1
+ import { afterParse, bool, define, emit, esc, HTMLBase, ICONS, msg, uid } from "./base.js";
2
2
  /**
3
3
  * پنجرهٔ گفتگو روی <dialog> بومی با showModal: فوکوس داخل پنجره می‌ماند، Esc می‌بندد،
4
4
  * بقیهٔ صفحه از دسترس صفحه‌خوان خارج می‌شود و پس از بستن، فوکوس به دکمهٔ بازکننده برمی‌گردد.
@@ -100,7 +100,7 @@ export class KashiDialog extends HTMLBase {
100
100
  d.setAttribute("aria-describedby", desc.id);
101
101
  else
102
102
  d.removeAttribute("aria-describedby");
103
- d.querySelector(".k-dialog__close").setAttribute("aria-label", this.getAttribute("close-label") ?? "بستن");
103
+ d.querySelector(".k-dialog__close").setAttribute("aria-label", this.getAttribute("close-label") ?? msg().common.close);
104
104
  }
105
105
  sync() {
106
106
  const d = this.dialog;
@@ -387,9 +387,9 @@ export class KashiCopyButton extends HTMLBase {
387
387
  }, Number(this.getAttribute("timeout")) || 2000);
388
388
  }
389
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;
390
+ const copy = this.getAttribute("label") ?? msg().copy.copy;
391
+ const copied = this.getAttribute("copied-label") ?? msg().copy.copied;
392
+ const text = this.state === "copied" ? copied : this.state === "failed" ? msg().copy.failed : copy;
393
393
  const iconOnly = bool(this, "icon-only");
394
394
  const icon = this.state === "copied" ? ICONS.check : ICONS.copy;
395
395
  const variant = this.getAttribute("variant") ?? "secondary";
package/dist/pickers.d.ts CHANGED
@@ -48,7 +48,7 @@ export declare class KashiMonthPicker extends HTMLBase {
48
48
  private out;
49
49
  private render;
50
50
  }
51
- /** حجم فایل به فارسی: «۱٫۲ مگابایت» */
51
+ /** حجم فایل به زبان فعلی: «۱٫۲ مگابایت» یا «1.2 MB» */
52
52
  export declare function formatBytes(bytes: number): string;
53
53
  /**
54
54
  * بارگذاری فایل با کشیدن و رها کردن، بررسی نوع و حجم و فهرست فایل‌ها.