diff --git a/.changeset/tidy-bushes-watch.md b/.changeset/tidy-bushes-watch.md new file mode 100644 index 000000000..a2295fbce --- /dev/null +++ b/.changeset/tidy-bushes-watch.md @@ -0,0 +1,5 @@ +--- +"@evo-web/marko": patch +--- + +Add evo-calendar component diff --git a/package-lock.json b/package-lock.json index f5d3868b1..6c4df1acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -298,6 +298,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2160,6 +2161,7 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -2995,6 +2997,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -3035,6 +3038,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -4699,6 +4703,7 @@ "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.62.tgz", "integrity": "sha512-aBmWraqvVMLzi3oZkm5cEAVTv4qtLIVl9tkci6M+duSaEbKq4+n9FuNjYlOA0/4slRmOeAOuYyi2BenzgKmQNA==", "license": "MIT", + "peer": true, "dependencies": { "@luxass/strip-json-comments": "^1.4.0", "complain": "^1.6.1", @@ -4737,6 +4742,7 @@ "integrity": "sha512-BxsS7EnvsFeHK0O64y6wSKk+8Dfe3c1OKfMbmYd4+NpxgUw6dyY20iTttp+6RZWmoUYXF1VBBmRxiFLAOZpfyA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@marko/run-explorer": "^2.0.1", "@marko/vite": "^5.4.2", @@ -4944,6 +4950,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -4967,6 +4974,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -5575,6 +5583,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -7519,6 +7528,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -7831,6 +7841,7 @@ "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -7848,6 +7859,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -7858,6 +7870,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -7928,6 +7941,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -8173,6 +8187,7 @@ "integrity": "sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/mocker": "4.0.18", "@vitest/utils": "4.0.18", @@ -8196,6 +8211,7 @@ "integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.0.18", "@vitest/mocker": "4.0.18", @@ -8553,6 +8569,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9479,6 +9496,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9977,7 +9995,8 @@ "resolved": "https://registry.npmjs.org/cldr-core/-/cldr-core-48.1.0.tgz", "integrity": "sha512-Br9lHQHRhFVyQ/4dY2AnTOuJVEwzDSFR9tE4EzaZxdtux4BFZ4V6oY0SiSAe/8H9+CJ6njpBiPJdDv+vdXnvfA==", "dev": true, - "license": "Unicode-3.0" + "license": "Unicode-3.0", + "peer": true }, "node_modules/cldr-dates-full": { "version": "48.1.0", @@ -10650,6 +10669,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -12201,6 +12221,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12323,6 +12344,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -12390,6 +12412,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -14572,6 +14595,7 @@ "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.5.0.tgz", "integrity": "sha512-uNSSv1KqRLNvkyXlf1FbeGWB1mJ/8/IYpVeqYiTIop5Wo8peUvNyzUfLa58vILsmXyz+XrETtgKIEOcSgBBKuQ==", "license": "https://www.highcharts.com/license", + "peer": true, "peerDependencies": { "jspdf": "^3.0.0", "svg2pdf.js": "^2.6.0" @@ -16447,6 +16471,7 @@ "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@lit/reactive-element": "^2.1.0", "lit-element": "^4.2.0", @@ -19234,6 +19259,7 @@ "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.1" }, @@ -19300,6 +19326,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -20065,6 +20092,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -20152,6 +20180,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20496,6 +20525,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20550,6 +20580,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -21367,6 +21398,7 @@ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -22425,6 +22457,7 @@ "integrity": "sha512-yueTpl5YJqLzQqs3CanxNdAAfFU23iP0j+JVJURE4ghfEtRmWfWoZWLGkVcyjmgum7UmjwAlqRuOjQDNvH89kw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", @@ -22877,6 +22910,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-syntax-patches-for-csstree": "^1.0.19", @@ -23146,6 +23180,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -23169,6 +23204,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -23761,7 +23797,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.21.0", @@ -23769,6 +23806,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -24431,6 +24469,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -24978,6 +25017,7 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -25812,6 +25852,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -26590,6 +26631,7 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -26798,6 +26840,7 @@ "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.38.tgz", "integrity": "sha512-dH5a6x/vcxb+fq/ZdWOTIuyz/Nf0kcdfWu9GwrGzl3AeM/FM2mAyp+szRssDmKYLw0VjxHiJTp3ddACSssVPPA==", "license": "MIT", + "peer": true, "dependencies": { "@marko/compiler": "^5.39.62", "@marko/runtime-tags": "^6.0.169", diff --git a/packages/evo-marko/package.json b/packages/evo-marko/package.json index a464ba2cf..661c75c81 100644 --- a/packages/evo-marko/package.json +++ b/packages/evo-marko/package.json @@ -27,6 +27,7 @@ "build:storybook": "storybook build -o ./_site", "deploy": "storybook build -o ./_site", "format": "eslint --fix; prettier . --write --log-level=warn", + "generate-locale-info": "node scripts/generate-locale-info && prettier --write src/common/dates/locale-info.ts", "importSVG": "tsx scripts/import-svg", "lint": "mtc && eslint && prettier . --check --log-level=warn", "prepublishOnly": "node scripts/prepublish", diff --git a/packages/evo-marko/scripts/generate-locale-info.js b/packages/evo-marko/scripts/generate-locale-info.js new file mode 100644 index 000000000..ea75ab2b4 --- /dev/null +++ b/packages/evo-marko/scripts/generate-locale-info.js @@ -0,0 +1,194 @@ +import fs from "fs"; +import weekData from "cldr-core/supplemental/weekData.json" with { type: "json" }; +import coverageLevels from "cldr-core/coverageLevels.json" with { type: "json" }; +import defaultContent from "cldr-core/defaultContent.json" with { type: "json" }; + +const output = { + _: { + y: "Y", + m: "M", + d: "D", + o: "ymd", + s: ["-", "-"], + w: 0, + }, +}; + +const firstDays = weekData.supplemental.weekData.firstDay; +const dayNums = { + sun: 0, + mon: 1, + tue: 2, + wed: 3, + thu: 4, + fri: 5, + sat: 6, +}; + +const dirPath = "../../node_modules/cldr-dates-full/main"; +const files = fs.readdirSync(dirPath); + +const modernBases = new Set( + Object.entries(coverageLevels.coverageLevels) + .filter(([, level]) => level === "modern") + .map(([locale]) => locale), +); +// Include sub-locales (e.g. en-GB, es-MX) whose base language is modern, +// since they can have functionally different date formats (order, separators, week start) +const modernLocales = new Set( + files.filter((locale) => { + const base = locale.split("-")[0]; + return modernBases.has(locale) || modernBases.has(base); + }), +); +const defaultCountries = Object.fromEntries( + defaultContent.defaultContent.map((locale) => { + const index = locale.lastIndexOf("-"); + return [locale.slice(0, index), locale.slice(index + 1)]; + }), +); + +for (const locale of files) { + if (!modernLocales.has(locale)) { + continue; + } + const parts = locale.split("-"); + let editing = output; + for (const part of parts) { + editing[part.toLowerCase()] ??= {}; + editing = editing[part.toLowerCase()]; + } + editing._ ??= {}; + editing = editing._; + + const dateFields = JSON.parse( + fs.readFileSync(`${dirPath}/${locale}/dateFields.json`), + ); + const caGeneric = JSON.parse( + fs.readFileSync(`${dirPath}/${locale}/ca-generic.json`), + ); + + // Returns null if CLDR has no localized display name (falls back to English "Year"/"Month"/"Day") + const getLetter = (time) => { + const name = dateFields.main[locale].dates.fields[time].displayName; + if (name === "Year" || name === "Month" || name === "Day") return null; + return [...name][0].toUpperCase(); + }; + const y = getLetter("year-narrow"); + const m = getLetter("month-narrow"); + const d = getLetter("day-narrow"); + if (y !== null) editing.y = y; + if (m !== null) editing.m = m; + if (d !== null) editing.d = d; + const { order, seps } = formatFromAvailable( + caGeneric.main[locale].dates.calendars.generic.dateTimeFormats + .availableFormats, + ); + editing.o = order; + editing.s = seps; + if (parts[1] && firstDays[parts[1]]) { + editing.w = dayNums[firstDays[parts[1]]]; + } else if (defaultCountries[locale] && firstDays[defaultCountries[locale]]) { + editing.w = dayNums[firstDays[defaultCountries[locale]]]; + } +} + +function formatFromAvailable(availableFormats) { + let { yyyyMd } = availableFormats; + yyyyMd = yyyyMd.replace(/G/g, "").trim().toLowerCase(); + let order = ""; + const seps = []; + for (const char of yyyyMd) { + if (char === "y" || char === "m" || char === "d") { + if (!order.length || order[order.length - 1] !== char) { + order += char; + seps.push(""); + } + } else { + seps[seps.length - 1] += char; + } + } + if (seps.at(-1) === "") { + seps.pop(); + } + + return { order, seps }; +} + +/** + * if any child `_` matches _exactly_ with its parent's `_`, prune it + */ +function pruneDuplication(obj, parent, parentKey) { + if (typeof obj === "object" && obj !== null) { + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null && key !== "_") { + pruneDuplication(obj[key], obj, key); + } + } + } + if (parent) { + if (JSON.stringify(obj._) === JSON.stringify(parent._)) { + delete parent[parentKey]; + } else if (parent._) { + for (const field in obj._) { + if (JSON.stringify(obj._[field]) === JSON.stringify(parent._[field])) { + delete obj._[field]; + } + } + // If all fields were pruned away, the entry itself is redundant + if ( + obj._ && + Object.keys(obj._).length === 0 && + Object.keys(obj).length === 1 + ) { + delete parent[parentKey]; + } + } + } +} + +pruneDuplication(output); + +/** + * Manual Overrides + */ +output.uz._.s = [".", "."]; +output.hy._.s = [".", "."]; +output.bg._.s = [".", "."]; +output.ar._.s = ["/", "/"]; +delete output.ar._.d; +delete output.ar._.m; +delete output.ar._.y; +output.yo._.d = "ọ"; +// fr-CA is no longer a "modern" locale in cldr v48 but Canada uses Sunday as first day of week +// so we ensure the fr.ca entry exists and override it manually +output.fr.ca ??= {}; +output.fr.ca._ = { w: 0 }; + +fs.writeFileSync( + "src/common/dates/locale-info.ts", + `// GENERATED FILE - DO NOT MODIFY +// Information pulled from cldr-core, see \`scripts/generate-date-info.js\` for more details + +export interface LocaleInfo { + /** order of year, month, and day in date format */ + o: \`\${"y" | "m" | "d"}\${"y" | "m" | "d"}\${"y" | "m" | "d"}\`; + /** separators between year, month, and day */ + s: string[]; + /** first day of week, 0 is Sunday */ + w: number; + /** letter representing day */ + d: string; + /** letter representing month */ + m: string; + /** letter representing year */ + y: string; +} + +export interface Locales { + _: Partial; + [country: string]: Locales | Partial; +} + +export default ${JSON.stringify(output)} as Locales;`, +); diff --git a/packages/evo-marko/src/common/dates/date-utils.ts b/packages/evo-marko/src/common/dates/date-utils.ts new file mode 100644 index 000000000..953bbcf06 --- /dev/null +++ b/packages/evo-marko/src/common/dates/date-utils.ts @@ -0,0 +1,90 @@ +import localeInfo, { type LocaleInfo, type Locales } from "./locale-info"; + +export type DayISO = `${number}-${number}-${number}`; +export type MonthISO = `${number}-${number}`; + +export function localeDefault(inputLocale?: string): string { + if (inputLocale) return inputLocale; + const locale = + (typeof navigator !== "undefined" && + (navigator.language || (navigator as any).userLanguage)) || + "en-US"; + try { + Intl.DateTimeFormat.supportedLocalesOf(locale); + return locale; + } catch { + return "en-US"; + } +} + +export function monthOffset(date: MonthISO, offset: number) { + let [year, month] = date.split("-").map(Number); + month -= 1; + year += Math.floor((month += offset) / 12); + month = (((month % 12) + 12) % 12) + 1; + return `${year}-${month < 10 ? "0" : ""}${month}` as MonthISO; +} + +export function getWeekdayInfo(localeName?: string) { + localeName = localeDefault(localeName); + const locale = getLocale(localeName); + const firstDayOfWeek = locale.w; + + const weekdayLabelFormatter = new Intl.DateTimeFormat(localeName, { + weekday: "short", + }); + const weekday = new Date(2022, 9, 2 + firstDayOfWeek); // October 2, 2022 was a Sunday + const weekdayLabels = [...Array(7)].map(() => { + const dayLabel = weekdayLabelFormatter.format(weekday); + weekday.setDate(weekday.getDate() + 1); + return dayLabel; + }); + + return { firstDayOfWeek, weekdayLabels }; +} + +const localeCache = new Map(); + +export function getLocale(locale?: string): LocaleInfo { + if (!locale) { + locale = localeDefault(); + } + + if (localeCache.has(locale)) { + return localeCache.get(locale) as LocaleInfo; + } + + let info = { ...localeInfo._ } as LocaleInfo; + + let curr = localeInfo; + const parts = locale.split("-"); + for (let part of parts) { + part = part.toLowerCase(); + if (curr[part]) { + curr = curr[part] as Locales; + info = { ...info, ...curr._ }; + } else { + break; + } + } + + localeCache.set(locale, info); + return info; +} + +/** + * ISO 8601 date format (YYYY-MM-DD) in **UTC** timezone + */ +export function toISO(date: Date) { + return date.toISOString().slice(0, 10) as DayISO; +} + +export function fromISO(iso: DayISO) { + return new Date(iso); +} + +export function offsetISO(iso: DayISO, days: number) { + const date = fromISO(iso); + date.setUTCDate(date.getUTCDate() + days); + return toISO(date); +} diff --git a/packages/evo-marko/src/common/dates/locale-info.ts b/packages/evo-marko/src/common/dates/locale-info.ts new file mode 100644 index 000000000..ecd969192 --- /dev/null +++ b/packages/evo-marko/src/common/dates/locale-info.ts @@ -0,0 +1,366 @@ +// GENERATED FILE - DO NOT MODIFY +// Information pulled from cldr-core, see `scripts/generate-date-info.js` for more details + +export interface LocaleInfo { + /** order of year, month, and day in date format */ + o: `${"y" | "m" | "d"}${"y" | "m" | "d"}${"y" | "m" | "d"}`; + /** separators between year, month, and day */ + s: string[]; + /** first day of week, 0 is Sunday */ + w: number; + /** letter representing day */ + d: string; + /** letter representing month */ + m: string; + /** letter representing year */ + y: string; +} + +export interface Locales { + _: Partial; + [country: string]: Locales | Partial; +} + +export default { + _: { y: "Y", m: "M", d: "D", o: "ymd", s: ["-", "-"], w: 0 }, + af: { _: { y: "J", o: "dmy", s: ["/", "/"] } }, + ak: { _: { y: "A", m: "B" } }, + am: { _: { y: "ዓ", m: "ወ", d: "ቀ" } }, + ar: { + _: { o: "dmy", s: ["/", "/"], w: 1 }, + bh: { _: { w: 6 } }, + dj: { _: { w: 6 } }, + dz: { _: { w: 6 } }, + eg: { _: { w: 6 } }, + il: { _: { w: 0 } }, + iq: { _: { w: 6 } }, + jo: { _: { w: 6 } }, + kw: { _: { w: 6 } }, + ly: { _: { w: 6 } }, + om: { _: { w: 6 } }, + qa: { _: { w: 6 } }, + sa: { _: { w: 0 } }, + sd: { _: { w: 6 } }, + sy: { _: { w: 6 } }, + ye: { _: { w: 0 } }, + }, + as: { _: { y: "ব", m: "ম", d: "দ", o: "dmy" } }, + az: { + _: { y: "I", m: "A", d: "G", o: "dmy", s: [".", "."] }, + arab: { _: { o: "ymd", s: ["-", "-"], w: 6 } }, + cyrl: { _: { w: 1 } }, + latn: { _: { w: 1 } }, + }, + ba: { _: { y: "Й", m: "А", d: "К", o: "dmy", s: [".", "."], w: 1 } }, + be: { _: { y: "Г", m: "М", d: "Д", o: "dmy", s: [".", "."], w: 1 } }, + bg: { _: { y: "Г", m: "М", d: "Д", o: "dmy", s: [".", "."], w: 1 } }, + bn: { _: { y: "ব", m: "ম", d: "দ", o: "dmy", s: ["/", "/"] } }, + bs: { + _: { y: "G", o: "dmy", s: [".", ".", "."] }, + cyrl: { _: { y: "Г", m: "М", d: "Д", w: 1 } }, + latn: { _: { w: 1 } }, + }, + ca: { + _: { y: "A", o: "dmy", s: ["/", "/"], w: 1 }, + es: { + valencia: { + _: { y: "A", m: "M", d: "D", o: "dmy", s: ["/", "/"], w: 1 }, + }, + }, + }, + chr: { _: { y: "Ꭴ", m: "Ꭷ", d: "Ꭲ", o: "mdy", s: ["/", "/"] } }, + cs: { _: { y: "R", o: "dmy", s: [". ", ". "], w: 1 } }, + cv: { _: { y: "Ҫ", m: "У", d: "К", s: [".", "."], w: 1 } }, + cy: { _: { y: "B", w: 1 } }, + da: { _: { y: "Å", o: "dmy", s: [".", "."], w: 1 } }, + de: { _: { y: "J", d: "T", o: "dmy", s: [".", "."], w: 1 } }, + dsb: { _: { y: "L", d: "Ź", o: "dmy", s: [".", "."], w: 1 } }, + el: { _: { y: "Έ", m: "Μ", d: "Η", o: "dmy", s: ["/", "/"], w: 1 } }, + en: { + "150": { _: { o: "dmy" } }, + _: { o: "mdy", s: ["/", "/"] }, + "001": { _: { o: "dmy", w: 1 } }, + ae: { _: { o: "dmy", w: 1 } }, + ag: { _: { o: "dmy" } }, + ai: { _: { o: "dmy", w: 1 } }, + at: { _: { o: "dmy", w: 1 } }, + au: { _: { o: "dmy", w: 1 } }, + bb: { _: { o: "dmy" } }, + be: { _: { o: "dmy", w: 1 } }, + bm: { _: { o: "dmy", w: 1 } }, + bs: { _: { o: "dmy" } }, + bw: { _: { o: "dmy" } }, + bz: { _: { o: "dmy" } }, + cc: { _: { o: "dmy" } }, + ch: { _: { o: "dmy", s: [".", "."], w: 1 } }, + ck: { _: { o: "dmy" } }, + cm: { _: { o: "dmy", w: 1 } }, + cx: { _: { o: "dmy" } }, + cy: { _: { o: "dmy", w: 1 } }, + cz: { _: { o: "dmy", w: 1 } }, + de: { _: { o: "dmy", w: 1 } }, + dg: { _: { o: "dmy" } }, + dk: { _: { o: "dmy", w: 1 } }, + dm: { _: { o: "dmy" } }, + dsrt: { _: { y: "𐐏", m: "𐐣", d: "𐐔", o: "ymd", s: ["-", "-"] } }, + ee: { _: { o: "dmy", w: 1 } }, + er: { _: { o: "dmy" } }, + es: { _: { o: "dmy", w: 1 } }, + fi: { _: { o: "dmy", w: 1 } }, + fj: { _: { o: "dmy", w: 1 } }, + fk: { _: { o: "dmy" } }, + fm: { _: { o: "dmy" } }, + fr: { _: { o: "dmy", w: 1 } }, + gb: { _: { o: "dmy", w: 1 } }, + gd: { _: { o: "dmy" } }, + ge: { _: { o: "dmy", w: 1 } }, + gg: { _: { o: "dmy" } }, + gh: { _: { o: "dmy" } }, + gi: { _: { o: "dmy" } }, + gm: { _: { o: "dmy" } }, + gs: { _: { o: "dmy" } }, + gy: { _: { o: "dmy" } }, + hk: { _: { o: "dmy" } }, + hu: { _: { o: "dmy", w: 1 } }, + id: { _: { o: "dmy" } }, + ie: { _: { o: "dmy", w: 1 } }, + il: { _: { o: "dmy" } }, + im: { _: { o: "dmy" } }, + in: { _: { o: "dmy" } }, + io: { _: { o: "dmy" } }, + it: { _: { o: "dmy", w: 1 } }, + je: { _: { o: "dmy" } }, + jm: { _: { o: "dmy" } }, + jp: { _: { o: "ymd" } }, + ke: { _: { o: "dmy" } }, + ki: { _: { o: "dmy" } }, + kn: { _: { o: "dmy" } }, + ky: { _: { o: "dmy" } }, + lc: { _: { o: "dmy" } }, + lr: { _: { o: "dmy" } }, + ls: { _: { o: "dmy" } }, + lt: { _: { o: "dmy", w: 1 } }, + lv: { _: { o: "dmy", w: 1 } }, + mg: { _: { o: "dmy" } }, + mo: { _: { o: "dmy" } }, + ms: { _: { o: "dmy" } }, + mt: { _: { o: "dmy" } }, + mu: { _: { o: "dmy" } }, + mv: { _: { o: "dmy", w: 5 } }, + mw: { _: { o: "dmy" } }, + my: { _: { o: "dmy", w: 1 } }, + na: { _: { o: "dmy" } }, + nf: { _: { o: "dmy" } }, + ng: { _: { o: "dmy" } }, + nl: { _: { o: "dmy", w: 1 } }, + no: { _: { o: "dmy", w: 1 } }, + nr: { _: { o: "dmy" } }, + nu: { _: { o: "dmy" } }, + nz: { _: { o: "dmy", w: 1 } }, + pg: { _: { o: "dmy" } }, + pk: { _: { o: "dmy" } }, + pl: { _: { o: "dmy", w: 1 } }, + pn: { _: { o: "dmy" } }, + pt: { _: { o: "dmy" } }, + pw: { _: { o: "dmy" } }, + ro: { _: { o: "dmy", w: 1 } }, + rw: { _: { o: "dmy" } }, + sb: { _: { o: "dmy" } }, + sc: { _: { o: "dmy" } }, + sd: { _: { o: "dmy", w: 6 } }, + se: { _: { o: "ymd", s: ["-", "-"], w: 1 } }, + sg: { _: { o: "dmy" } }, + sh: { _: { o: "dmy" } }, + si: { _: { o: "dmy", w: 1 } }, + sk: { _: { o: "dmy", w: 1 } }, + sl: { _: { o: "dmy" } }, + ss: { _: { o: "dmy" } }, + sx: { _: { o: "dmy" } }, + sz: { _: { o: "dmy" } }, + shaw: { _: { y: "𐑘", m: "𐑥", d: "𐑛", o: "ymd", s: ["-", "-"], w: 1 } }, + tc: { _: { o: "dmy" } }, + tk: { _: { o: "dmy" } }, + to: { _: { o: "dmy" } }, + tt: { _: { o: "dmy" } }, + tv: { _: { o: "dmy" } }, + tz: { _: { o: "dmy" } }, + ua: { _: { o: "dmy", w: 1 } }, + ug: { _: { o: "dmy" } }, + vc: { _: { o: "dmy" } }, + vg: { _: { o: "dmy" } }, + vu: { _: { o: "dmy" } }, + ws: { _: { o: "dmy" } }, + za: { _: { o: "ymd" } }, + zm: { _: { o: "dmy" } }, + zw: { _: { o: "dmy" } }, + }, + es: { + _: { y: "A", o: "dmy", s: ["/", "/"], w: 1 }, + br: { _: { w: 0 } }, + bz: { _: { w: 0 } }, + cl: { _: { s: ["-", "-"] } }, + co: { _: { w: 0 } }, + do: { _: { w: 0 } }, + gt: { _: { w: 0 } }, + hn: { _: { w: 0 } }, + mx: { _: { w: 0 } }, + ni: { _: { w: 0 } }, + pa: { _: { w: 0 } }, + pe: { _: { w: 0 } }, + ph: { _: { w: 0 } }, + pr: { _: { o: "mdy", w: 0 } }, + py: { _: { w: 0 } }, + sv: { _: { w: 0 } }, + us: { _: { w: 0 } }, + ve: { _: { w: 0 } }, + }, + et: { _: { y: "A", m: "K", d: "P", o: "dmy", s: [".", "."], w: 1 } }, + eu: { _: { y: "U", m: "H", d: "E", s: ["/", "/"], w: 1 } }, + fa: { _: { y: "س", m: "م", d: "ر", s: ["/", "/"], w: 6 } }, + fi: { _: { y: "V", m: "K", d: "P", o: "dmy", s: [".", "."], w: 1 } }, + fil: { _: { y: "T", m: "B", d: "A", o: "mdy", s: ["/", "/"] } }, + fr: { + _: { y: "A", d: "J", o: "dmy", s: ["/", "/"], w: 1 }, + ca: { _: { w: 0 } }, + dj: { _: { w: 6 } }, + dz: { _: { w: 6 } }, + sy: { _: { w: 6 } }, + }, + ga: { _: { y: "B", d: "L", o: "dmy", s: ["/", "/"], w: 1 } }, + gd: { _: { y: "B", d: "L", o: "dmy", s: ["/", "/"], w: 1 } }, + gl: { _: { y: "A", o: "dmy", s: ["/", "/"], w: 1 } }, + gu: { _: { y: "વ", m: "મ", d: "દ", o: "dmy", s: ["/", "/ "] } }, + ha: { _: { y: "S", m: "W", d: "K" } }, + he: { _: { y: "ש", m: "ח", d: "י", o: "dmy", s: [".", "."] } }, + hi: { + _: { y: "व", m: "म", d: "द", o: "dmy", s: ["/", "/"] }, + latn: { _: { y: "Y", m: "M", d: "D" } }, + }, + hr: { _: { y: "G", o: "dmy", s: [". ", ". ", "."], w: 1 } }, + hsb: { _: { y: "L", o: "dmy", s: [".", "."], w: 1 } }, + hu: { _: { y: "É", m: "H", d: "N", s: [". ", ". ", "."], w: 1 } }, + hy: { _: { y: "Տ", m: "Ա", d: "Օ", o: "dmy", s: [".", "."], w: 1 } }, + id: { _: { y: "T", m: "B", d: "H", o: "dmy", s: ["/", "/"] } }, + ig: { _: { y: "A", m: "Ọ", d: "Ụ" } }, + is: { _: { y: "Á", o: "dmy", s: [".", "."] } }, + it: { _: { y: "A", d: "G", o: "dmy", s: ["/", "/"], w: 1 } }, + ja: { _: { y: "年", m: "月", d: "日", s: ["/", "/"] } }, + jv: { _: { y: "T", m: "S", o: "dmy", s: [" - ", " - "] } }, + ka: { _: { y: "Წ", m: "Თ", d: "Დ", o: "dmy", s: [".", "."], w: 1 } }, + kk: { + _: { y: "Ж", m: "А", d: "К", o: "dmy", s: [".", "."] }, + arab: { _: { y: "ج", m: "ا", d: "ك", o: "mdy", s: ["/", "/"], w: 1 } }, + cyrl: { _: { w: 1 } }, + kz: { _: { w: 1 } }, + }, + km: { _: { y: "ឆ", m: "ខ", d: "ថ", o: "dmy", s: ["/", "/"] } }, + kn: { _: { y: "ವ", m: "ತ", d: "ದ" } }, + ko: { + _: { y: "년", m: "월", d: "일", s: [". ", ". ", "."] }, + cn: { _: { w: 1 } }, + }, + kok: { + _: { y: "व", m: "म", d: "द", o: "dmy" }, + deva: { _: { w: 0 } }, + latn: { _: { y: "V", m: "M", d: "D", w: 0 } }, + }, + ky: { _: { y: "Ж", m: "А", d: "К", o: "ydm", w: 1 } }, + lo: { _: { y: "ປ", m: "ດ", d: "ມ", o: "dmy", s: ["/", "/"] } }, + lt: { _: { y: "M", w: 1 } }, + lv: { _: { y: "G", o: "dmy", s: [".", ".", "."], w: 1 } }, + mk: { _: { y: "Г", m: "М", d: "Д", o: "dmy", s: [".", ".", " 'г'."], w: 1 } }, + ml: { _: { y: "വ", m: "മ", d: "ദ", o: "dmy", s: ["/", "/"] } }, + mn: { + _: { y: "Ж", m: "С", d: "Ө", s: [".", "."], w: 1 }, + mong: { _: { s: ["-", "-"] }, mn: { _: { y: "ᠵ", m: "ᠰ", d: "ᠡ" } } }, + }, + mr: { _: { y: "व", m: "म", d: "द", o: "dmy", s: ["/", "/"] } }, + ms: { + _: { y: "T", m: "B", d: "H", o: "dmy", s: ["/", "/"], w: 1 }, + arab: { _: { o: "ymd", s: ["-", "-"] } }, + id: { _: { w: 0 } }, + sg: { _: { w: 0 } }, + }, + my: { _: { y: "န", m: "လ", d: "ရ", o: "dmy", s: ["/", "/"] } }, + ne: { _: { y: "व", m: "म", d: "ब" } }, + nl: { _: { y: "J", o: "dmy", w: 1 }, be: { _: { s: ["/", "/"] } } }, + nn: { _: { y: "Å", o: "dmy", s: [".", "."], w: 1 } }, + no: { _: { y: "Å", o: "dmy", s: [".", "."] } }, + or: { _: { y: "ବ", m: "ମ", d: "ଦ", o: "mdy", s: ["/", "/"] } }, + pa: { + _: { y: "ਸ", m: "ਮ", d: "ਦ", o: "dmy", s: ["/", "/"] }, + arab: { _: { y: "و", m: "م", d: "د", o: "ymd", s: ["-", "-"], w: 0 } }, + guru: { _: { w: 0 } }, + }, + pl: { _: { y: "R", o: "dmy", s: [".", "."], w: 1 } }, + ps: { _: { y: "ک", m: "م", d: "و", w: 6 }, pk: { _: { w: 0 } } }, + pt: { + _: { y: "A", o: "dmy", s: ["/", "/"] }, + ch: { _: { w: 1 } }, + lu: { _: { w: 1 } }, + }, + qu: { _: { y: "W", m: "K", d: "P", o: "dmy" }, ec: { _: { w: 1 } } }, + rm: { _: { y: "O", o: "dmy", w: 1 } }, + ro: { _: { y: "A", m: "L", d: "Z", o: "dmy", s: [".", "."], w: 1 } }, + ru: { _: { y: "Г", m: "М", d: "Д", o: "dmy", s: [".", "."], w: 1 } }, + sd: { + _: { y: "س", m: "م", d: "ڏ" }, + arab: { _: { w: 0 } }, + deva: { _: { y: "स", m: "म", d: "द", w: 0 } }, + }, + shn: { _: { y: "ပ", m: "လ", d: "ဝ" } }, + si: { _: { y: "ව", m: "ම", d: "ද", w: 1 } }, + sk: { _: { y: "R", o: "dmy", s: [". ", ". "], w: 1 } }, + sl: { _: { y: "L", o: "dmy", s: [". ", ". "], w: 1 } }, + so: { + _: { y: "S", m: "B", d: "M", o: "mdy", s: ["/", "/"] }, + dj: { _: { w: 6 } }, + et: { _: { w: 0 } }, + ke: { _: { w: 0 } }, + }, + sq: { _: { y: "V", o: "dmy", s: [".", "."], w: 1 } }, + sr: { + _: { y: "Г", m: "М", d: "Д", o: "dmy", s: [".", ".", "."] }, + cyrl: { _: { w: 1 } }, + latn: { _: { y: "G", m: "M", d: "D", w: 1 } }, + }, + sv: { _: { y: "Å", w: 1 } }, + sw: { _: { y: "M", d: "S", o: "dmy", s: ["/", "/"] }, ke: { _: { w: 0 } } }, + ta: { + _: { y: "ஆ", m: "ம", d: "ந", o: "dmy", s: ["/", "/"] }, + lk: { _: { w: 1 } }, + my: { _: { w: 1 } }, + }, + te: { _: { y: "స", m: "న", d: "ర", o: "dmy", s: ["/", "/"] } }, + th: { _: { y: "ป", m: "เ", d: "ว", o: "dmy", s: ["/", "/"] } }, + ti: { _: { y: "ዓ", m: "ወ", d: "መ", o: "mdy", s: ["/", "/"] } }, + tk: { _: { y: "Ý", m: "A", d: "G", w: 1 } }, + tr: { _: { m: "A", d: "G", o: "dmy", s: [".", "."], w: 1 } }, + uk: { _: { y: "Р", m: "М", d: "Д", o: "dmy", s: [".", "."], w: 1 } }, + ur: { _: { y: "س", m: "م", d: "د", o: "dmy", s: ["/", "/"] } }, + uz: { + _: { m: "O", d: "K", o: "dmy", s: [".", "."] }, + arab: { _: { o: "ymd", s: ["-", "-"], w: 6 } }, + cyrl: { _: { y: "Й", m: "О", d: "К", o: "ymd", s: ["-", "-"], w: 1 } }, + latn: { _: { w: 1 } }, + }, + vi: { _: { y: "N", m: "T", d: "N", o: "dmy", s: ["/", "/"], w: 1 } }, + yo: { _: { y: "Ọ", m: "O", d: "ọ" }, bj: { _: { y: "Ɔ", d: "Ɔ" } } }, + yue: { + _: { y: "年", m: "月", d: "日", s: ["/", "/"] }, + hans: { _: { w: 1 } }, + hant: { _: { w: 0 } }, + }, + zh: { + _: { y: "年", m: "月", d: "日", s: ["/", "/"] }, + hans: { + _: { w: 1 }, + hk: { _: { o: "dmy" } }, + mo: { _: { s: ["年", "月", "日"] } }, + sg: { _: { s: ["年", "月", "日"] } }, + }, + hant: { _: { w: 0 } }, + latn: { _: { s: ["-", "-"], w: 1 } }, + }, + zu: { _: { y: "U", m: "I", d: "U", o: "mdy", s: ["/", "/"] } }, +} as Locales; diff --git a/packages/evo-marko/src/tags/evo-calendar/README.md b/packages/evo-marko/src/tags/evo-calendar/README.md new file mode 100644 index 000000000..a5eb82e8d --- /dev/null +++ b/packages/evo-marko/src/tags/evo-calendar/README.md @@ -0,0 +1,13 @@ +

+ + evo-calendar + +

+ +Calendar component for displaying dates and date ranges. Used as a building block inside date pickers and date range pickers. + +## Examples and Documentation + +- [Storybook](https://ebay.github.io/evo-web/ebayui-core/?path=/story/form-input-evo-calendar) +- [Storybook Docs](https://ebay.github.io/evo-web/ebayui-core/?path=/docs/form-input-evo-calendar) +- [Code Examples](https://github.com/eBay/evo-web/tree/main/packages/evo-marko/src/tags/evo-calendar/examples) diff --git a/packages/evo-marko/src/tags/evo-calendar/calendar.stories.ts b/packages/evo-marko/src/tags/evo-calendar/calendar.stories.ts new file mode 100644 index 000000000..53c0f4e91 --- /dev/null +++ b/packages/evo-marko/src/tags/evo-calendar/calendar.stories.ts @@ -0,0 +1,117 @@ +import { buildExtensionTemplate } from "../../common/storybook/utils"; +import { type Meta } from "@storybook/marko"; +import Readme from "./README.md"; +import Component, { type Input } from "./index.marko"; +import DefaultTemplate from "./examples/default.marko"; +import DefaultCode from "./examples/default.marko?raw"; +import ControllableTemplate from "./examples/controllable.marko"; +import ControllableCode from "./examples/controllable.marko?raw"; +import WithLinksTemplate from "./examples/with-links.marko"; +import WithLinksCode from "./examples/with-links.marko?raw"; + +export default { + title: "building blocks/evo-calendar", + component: Component, + parameters: { + docs: { + description: { component: Readme }, + }, + }, + argTypes: { + selectMode: { + control: "inline-radio", + options: [undefined, "day", "range"], + description: + 'When set, day cells render as `