@roxyapi/ui 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/LICENSE +21 -0
- package/README.md +505 -0
- package/THEMING.md +24 -7
- package/dist/cdn/components/biorhythm-chart.js +15 -22
- package/dist/cdn/components/biorhythm-chart.js.map +3 -3
- package/dist/cdn/components/compatibility-card.js +36 -34
- package/dist/cdn/components/compatibility-card.js.map +4 -4
- package/dist/cdn/components/dasha-timeline.js +35 -39
- package/dist/cdn/components/dasha-timeline.js.map +4 -4
- package/dist/cdn/components/data.js +6 -6
- package/dist/cdn/components/data.js.map +3 -3
- package/dist/cdn/components/dosha-card.js +13 -13
- package/dist/cdn/components/dosha-card.js.map +2 -2
- package/dist/cdn/components/endpoint-form.js +47 -28
- package/dist/cdn/components/endpoint-form.js.map +3 -3
- package/dist/cdn/components/guna-milan.js +18 -18
- package/dist/cdn/components/guna-milan.js.map +4 -4
- package/dist/cdn/components/hexagram.js +26 -26
- package/dist/cdn/components/hexagram.js.map +3 -3
- package/dist/cdn/components/horoscope-card.js +38 -38
- package/dist/cdn/components/horoscope-card.js.map +3 -3
- package/dist/cdn/components/kp-planets-table.js +10 -10
- package/dist/cdn/components/kp-planets-table.js.map +4 -4
- package/dist/cdn/components/location-search.js +6 -6
- package/dist/cdn/components/location-search.js.map +3 -3
- package/dist/cdn/components/moon-phase.js +21 -21
- package/dist/cdn/components/moon-phase.js.map +4 -4
- package/dist/cdn/components/natal-chart.js +61 -19
- package/dist/cdn/components/natal-chart.js.map +4 -4
- package/dist/cdn/components/numerology-card.js +40 -31
- package/dist/cdn/components/numerology-card.js.map +3 -3
- package/dist/cdn/components/panchang-table.js +25 -25
- package/dist/cdn/components/panchang-table.js.map +4 -4
- package/dist/cdn/components/synastry-chart.js +129 -39
- package/dist/cdn/components/synastry-chart.js.map +4 -4
- package/dist/cdn/components/tarot-card.js +49 -20
- package/dist/cdn/components/tarot-card.js.map +3 -3
- package/dist/cdn/components/tarot-spread.js +43 -27
- package/dist/cdn/components/tarot-spread.js.map +3 -3
- package/dist/cdn/components/vedic-kundli.js +23 -9
- package/dist/cdn/components/vedic-kundli.js.map +3 -3
- package/dist/cdn/roxy-ui.js +560 -350
- package/dist/cdn/roxy-ui.js.map +4 -4
- package/dist/components/biorhythm-chart.d.ts +2 -46
- package/dist/components/biorhythm-chart.d.ts.map +1 -1
- package/dist/components/biorhythm-chart.js +24 -23
- package/dist/components/biorhythm-chart.js.map +2 -2
- package/dist/components/compatibility-card.d.ts +2 -27
- package/dist/components/compatibility-card.d.ts.map +1 -1
- package/dist/components/compatibility-card.js +50 -29
- package/dist/components/compatibility-card.js.map +3 -3
- package/dist/components/dasha-timeline.d.ts +2 -31
- package/dist/components/dasha-timeline.d.ts.map +1 -1
- package/dist/components/dasha-timeline.js +32 -30
- package/dist/components/dasha-timeline.js.map +3 -3
- package/dist/components/data.d.ts +6 -0
- package/dist/components/data.d.ts.map +1 -1
- package/dist/components/data.js +9 -1
- package/dist/components/data.js.map +2 -2
- package/dist/components/dosha-card.d.ts +2 -16
- package/dist/components/dosha-card.d.ts.map +1 -1
- package/dist/components/dosha-card.js +12 -13
- package/dist/components/dosha-card.js.map +2 -2
- package/dist/components/endpoint-form.d.ts +2 -0
- package/dist/components/endpoint-form.d.ts.map +1 -1
- package/dist/components/endpoint-form.js +66 -8
- package/dist/components/endpoint-form.js.map +2 -2
- package/dist/components/guna-milan.d.ts +2 -20
- package/dist/components/guna-milan.d.ts.map +1 -1
- package/dist/components/guna-milan.js +22 -12
- package/dist/components/guna-milan.js.map +3 -3
- package/dist/components/hexagram.d.ts +3 -27
- package/dist/components/hexagram.d.ts.map +1 -1
- package/dist/components/hexagram.js +31 -15
- package/dist/components/hexagram.js.map +2 -2
- package/dist/components/horoscope-card.d.ts +2 -20
- package/dist/components/horoscope-card.d.ts.map +1 -1
- package/dist/components/horoscope-card.js +24 -15
- package/dist/components/horoscope-card.js.map +2 -2
- package/dist/components/kp-planets-table.d.ts +2 -21
- package/dist/components/kp-planets-table.d.ts.map +1 -1
- package/dist/components/kp-planets-table.js +10 -4
- package/dist/components/kp-planets-table.js.map +3 -3
- package/dist/components/location-search.d.ts +3 -11
- package/dist/components/location-search.d.ts.map +1 -1
- package/dist/components/location-search.js +45 -5
- package/dist/components/location-search.js.map +2 -2
- package/dist/components/moon-phase.d.ts +4 -21
- package/dist/components/moon-phase.d.ts.map +1 -1
- package/dist/components/moon-phase.js +17 -4
- package/dist/components/moon-phase.js.map +3 -3
- package/dist/components/natal-chart.d.ts +7 -43
- package/dist/components/natal-chart.d.ts.map +1 -1
- package/dist/components/natal-chart.js +130 -70
- package/dist/components/natal-chart.js.map +3 -3
- package/dist/components/numerology-card.d.ts +5 -37
- package/dist/components/numerology-card.d.ts.map +1 -1
- package/dist/components/numerology-card.js +54 -28
- package/dist/components/numerology-card.js.map +2 -2
- package/dist/components/panchang-table.d.ts +3 -62
- package/dist/components/panchang-table.d.ts.map +1 -1
- package/dist/components/panchang-table.js +62 -32
- package/dist/components/panchang-table.js.map +3 -3
- package/dist/components/synastry-chart.d.ts +9 -28
- package/dist/components/synastry-chart.d.ts.map +1 -1
- package/dist/components/synastry-chart.js +178 -38
- package/dist/components/synastry-chart.js.map +3 -3
- package/dist/components/tarot-card.d.ts +5 -29
- package/dist/components/tarot-card.d.ts.map +1 -1
- package/dist/components/tarot-card.js +59 -20
- package/dist/components/tarot-card.js.map +2 -2
- package/dist/components/tarot-spread.d.ts +2 -24
- package/dist/components/tarot-spread.d.ts.map +1 -1
- package/dist/components/tarot-spread.js +39 -13
- package/dist/components/tarot-spread.js.map +2 -2
- package/dist/components/vedic-kundli.d.ts +3 -23
- package/dist/components/vedic-kundli.d.ts.map +1 -1
- package/dist/components/vedic-kundli.js +25 -13
- package/dist/components/vedic-kundli.js.map +2 -2
- package/dist/index.cjs +1149 -358
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1149 -358
- package/dist/index.js.map +4 -4
- package/dist/manifest.d.ts +49 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/styles/tokens.css +47 -1
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/types.gen.d.ts +27811 -0
- package/dist/types/types.gen.d.ts.map +1 -0
- package/dist/utils/debounce.d.ts +9 -1
- package/dist/utils/debounce.d.ts.map +1 -1
- package/dist/utils/format.d.ts +15 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/components/biorhythm-chart.ts +39 -84
- package/src/components/compatibility-card.ts +85 -52
- package/src/components/dasha-timeline.ts +55 -73
- package/src/components/data.ts +20 -1
- package/src/components/dosha-card.ts +18 -31
- package/src/components/endpoint-form.ts +79 -11
- package/src/components/guna-milan.ts +16 -34
- package/src/components/hexagram.ts +53 -43
- package/src/components/horoscope-card.ts +51 -39
- package/src/components/kp-planets-table.ts +8 -27
- package/src/components/location-search.ts +45 -20
- package/src/components/moon-phase.ts +28 -25
- package/src/components/natal-chart.ts +129 -84
- package/src/components/numerology-card.ts +87 -79
- package/src/components/panchang-table.ts +40 -78
- package/src/components/synastry-chart.ts +220 -78
- package/src/components/tarot-card.ts +76 -62
- package/src/components/tarot-spread.ts +72 -45
- package/src/components/vedic-kundli.ts +42 -51
- package/src/index.ts +14 -24
- package/src/manifest.ts +366 -0
- package/src/styles/tokens.css +47 -1
- package/src/tokens/index.ts +5 -0
- package/src/types/types.gen.ts +1 -1
- package/src/utils/debounce.ts +23 -4
- package/src/utils/format.ts +57 -0
- package/src/version.ts +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/components/location-search.ts", "../../src/utils/base-styles.ts", "../../src/utils/debounce.ts"],
|
|
4
|
-
"sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { debounce } from '../utils/debounce.js';\n\nexport interface CityResult {\n\tcity: string;\n\tprovince?: string;\n\tcountry: string;\n\tiso2?: string;\n\tlatitude: number;\n\tlongitude: number;\n\ttimezone: string;\n\tutcOffset: number;\n\tpopulation?: number;\n}\n\ninterface CitySearchResponse {\n\ttotal?: number;\n\tcities?: CityResult[];\n}\n\n/**\n * Stateful location search input. Calls /location/search and emits\n * `roxy-location-select` CustomEvent with the chosen city. Required for any\n * chart endpoint.\n *\n * Lifted from jyotish-vedic-astrology-app/src/components/city-search.tsx,\n * keeping the 300ms debounce and click-outside behavior, replacing React\n * state with Lit reactive properties and using direct fetch to RoxyAPI.\n *\n * Attributes:\n * api-key optional. Direct call to roxyapi.com when set.\n * publishable-key optional. Browser-safe pk_* key with allowed_origins.\n * endpoint optional. Override URL (default https://roxyapi.com/api/v2/location/search).\n * placeholder optional. Input placeholder.\n * default-value optional. Pre-filled query.\n */\n@customElement('roxy-location-search')\nexport class RoxyLocationSearch extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t:host {\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t\t.field {\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t\tinput {\n\t\t\t\twidth: 100%;\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);\n\t\t\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\t\t\tfont-family: inherit;\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\ttransition:\n\t\t\t\t\tborder-color var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t\tbox-sizing: border-box;\n\t\t\t}\n\t\t\tinput:focus {\n\t\t\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\t\t\toutline-offset: 2px;\n\t\t\t\tborder-color: var(--roxy-accent-fg, #b45309);\n\t\t\t}\n\t\t\t.spinner {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 12px;\n\t\t\t\ttop: 50%;\n\t\t\t\ttransform: translateY(-50%);\n\t\t\t\twidth: 14px;\n\t\t\t\theight: 14px;\n\t\t\t\tborder: 2px solid var(--roxy-muted, #71717a);\n\t\t\t\tborder-top-color: transparent;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tanimation: roxy-spin 700ms linear infinite;\n\t\t\t}\n\t\t\t@keyframes roxy-spin {\n\t\t\t\tto {\n\t\t\t\t\ttransform: translateY(-50%) rotate(360deg);\n\t\t\t\t}\n\t\t\t}\n\t\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t\t.spinner {\n\t\t\t\t\tanimation: none;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.results {\n\t\t\t\tposition: absolute;\n\t\t\t\tz-index: 50;\n\t\t\t\ttop: calc(100% + 4px);\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tbox-shadow: var(--roxy-shadow-md);\n\t\t\t\tmax-height: 22rem;\n\t\t\t\toverflow-y: auto;\n\t\t\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.option {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: baseline;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\twidth: 100%;\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: transparent;\n\t\t\t\tborder: 0;\n\t\t\t\ttext-align: left;\n\t\t\t\tfont-family: inherit;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tcursor: pointer;\n\t\t\t\ttransition: background-color var(--roxy-motion-duration, 200ms);\n\t\t\t}\n\t\t\t.option:hover,\n\t\t\t.option[aria-selected='true'] {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-accent, #f59e0b) 10%, transparent);\n\t\t\t}\n\t\t\t.option .city {\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\t\t\t.option .where {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tflex-grow: 1;\n\t\t\t}\n\t\t\t.option .tz {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t}\n\t\t\t.empty {\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ type: String, attribute: 'api-key' })\n\tapiKey?: string;\n\n\t@property({ type: String, attribute: 'publishable-key' })\n\tpublishableKey?: string;\n\n\t@property({ type: String })\n\tendpoint = 'https://roxyapi.com/api/v2/location/search';\n\n\t@property({ type: String })\n\tplaceholder = 'Search city';\n\n\t@property({ type: String, attribute: 'default-value' })\n\tdefaultValue = '';\n\n\t@state()\n\tprivate query = '';\n\n\t@state()\n\tprivate results: CityResult[] = [];\n\n\t@state()\n\tprivate isOpen = false;\n\n\t@state()\n\tprivate isLoading = false;\n\n\t@state()\n\tprivate highlight = -1;\n\n\tprivate clickOutsideHandler?: (e: MouseEvent) => void;\n\tprivate debouncedFetch = debounce((q: string) => {\n\t\tvoid this.fetchResults(q);\n\t}, 300);\n\n\tconnectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tthis.query = this.defaultValue;\n\t\tthis.clickOutsideHandler = (e: MouseEvent) => {\n\t\t\tconst path = e.composedPath();\n\t\t\tif (!path.includes(this)) this.isOpen = false;\n\t\t};\n\t\tdocument.addEventListener('mousedown', this.clickOutsideHandler);\n\t}\n\n\tdisconnectedCallback(): void {\n\t\tsuper.disconnectedCallback();\n\t\tif (this.clickOutsideHandler) {\n\t\t\tdocument.removeEventListener('mousedown', this.clickOutsideHandler);\n\t\t}\n\t}\n\n\tprivate async fetchResults(q: string) {\n\t\tthis.isLoading = true;\n\t\ttry {\n\t\t\tconst url = new URL(this.endpoint);\n\t\t\turl.searchParams.set('q', q);\n\t\t\turl.searchParams.set('limit', '8');\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAccept: 'application/json',\n\t\t\t};\n\t\t\tif (this.apiKey) headers['X-API-Key'] = this.apiKey;\n\t\t\tif (this.publishableKey) headers['X-API-Key'] = this.publishableKey;\n\t\t\tconst res = await fetch(url, { headers });\n\t\t\tif (!res.ok) throw new Error(`HTTP ${res.status}`);\n\t\t\tconst json = (await res.json()) as CitySearchResponse;\n\t\t\tthis.results = json.cities ?? [];\n\t\t\tthis.isOpen = this.results.length > 0;\n\t\t\tthis.highlight = this.results.length > 0 ? 0 : -1;\n\t\t} catch (_err) {\n\t\t\tthis.results = [];\n\t\t\tthis.isOpen = false;\n\t\t} finally {\n\t\t\tthis.isLoading = false;\n\t\t}\n\t}\n\n\tprivate onInput = (e: Event) => {\n\t\tconst value = (e.target as HTMLInputElement).value;\n\t\tthis.query = value;\n\t\tif (value.length < 2) {\n\t\t\tthis.results = [];\n\t\t\tthis.isOpen = false;\n\t\t\tthis.highlight = -1;\n\t\t\treturn;\n\t\t}\n\t\tthis.debouncedFetch(value);\n\t};\n\n\tprivate select(city: CityResult) {\n\t\tthis.query = `${city.city}${city.province ? `, ${city.province}` : ''}, ${city.country}`;\n\t\tthis.isOpen = false;\n\t\tthis.results = [];\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent('roxy-location-select', {\n\t\t\t\tdetail: city,\n\t\t\t\tbubbles: true,\n\t\t\t\tcomposed: true,\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate onKeyDown = (e: KeyboardEvent) => {\n\t\tif (!this.isOpen || this.results.length === 0) {\n\t\t\tif (e.key === 'ArrowDown' && this.query.length >= 2) {\n\t\t\t\tvoid this.fetchResults(this.query);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (e.key === 'ArrowDown') {\n\t\t\te.preventDefault();\n\t\t\tthis.highlight = (this.highlight + 1) % this.results.length;\n\t\t} else if (e.key === 'ArrowUp') {\n\t\t\te.preventDefault();\n\t\t\tthis.highlight =\n\t\t\t\t(this.highlight - 1 + this.results.length) % this.results.length;\n\t\t} else if (e.key === 'Enter') {\n\t\t\te.preventDefault();\n\t\t\tconst target = this.results[this.highlight] ?? this.results[0];\n\t\t\tif (target) this.select(target);\n\t\t} else if (e.key === 'Escape') {\n\t\t\tthis.isOpen = false;\n\t\t}\n\t};\n\n\trender() {\n\t\treturn html`<div class=\"field\">\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\trole=\"combobox\"\n\t\t\t\taria-expanded=${this.isOpen ? 'true' : 'false'}\n\t\t\t\taria-controls=\"roxy-location-listbox\"\n\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tplaceholder=${this.placeholder}\n\t\t\t\t.value=${this.query}\n\t\t\t\t@input=${this.onInput}\n\t\t\t\t@keydown=${this.onKeyDown}\n\t\t\t\t@focus=${() => {\n\t\t\t\t\tif (this.results.length > 0) this.isOpen = true;\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t${this.isLoading ? html`<span class=\"spinner\" role=\"status\" aria-label=\"Loading\"></span>` : nothing}\n\t\t\t${\n\t\t\t\tthis.isOpen\n\t\t\t\t\t? html`<ul\n\t\t\t\t\t\tid=\"roxy-location-listbox\"\n\t\t\t\t\t\tclass=\"results\"\n\t\t\t\t\t\trole=\"listbox\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tthis.results.length === 0\n\t\t\t\t\t\t\t\t? html`<li class=\"empty\" role=\"status\">No cities found</li>`\n\t\t\t\t\t\t\t\t: this.results.map(\n\t\t\t\t\t\t\t\t\t\t(city, idx) => html`<li role=\"presentation\">\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tclass=\"option\"\n\t\t\t\t\t\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\t\t\t\t\t\taria-selected=${this.highlight === idx ? 'true' : 'false'}\n\t\t\t\t\t\t\t\t\t\t\t@click=${() => this.select(city)}\n\t\t\t\t\t\t\t\t\t\t\t@mouseenter=${() => {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.highlight = idx;\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"city\">${city.city}</span>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"where\"\n\t\t\t\t\t\t\t\t\t\t\t\t>${city.province ? html`${city.province}, ` : ''}${city.country}</span\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"tz\"\n\t\t\t\t\t\t\t\t\t\t\t\t>UTC${city.utcOffset >= 0 ? '+' : ''}${city.utcOffset}</span\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</li>`,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t</ul>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-location-search': RoxyLocationSearch;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Lightweight debounce for input handlers. Used by location search.\n */\nexport function debounce<F extends (...args: never[]) => unknown>(\n\tfn: F,\n\twait: number,\n): F {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\treturn ((...args: Parameters<F>) => {\n\t\tif (timer) clearTimeout(timer);\n\t\ttimer = setTimeout(() => fn(...args), wait);\n\t}) as F;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;AAAA,SAAS,OAAAA,MAAK,MAAM,YAAY,eAAe;AAC/C,SAAS,eAAe,UAAU,aAAa;;;ACD/C,SAAS,WAAW;AAMb,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;
|
|
4
|
+
"sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { SearchCitiesResponse } from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { debounce } from '../utils/debounce.js';\n\ntype CityResult = SearchCitiesResponse['cities'][number];\n\n/**\n * Stateful location search input. Calls /location/search and emits\n * `roxy-location-select` CustomEvent with the chosen city. Required for any\n * chart endpoint.\n *\n * Lifted from jyotish-vedic-astrology-app/src/components/city-search.tsx,\n * keeping the 300ms debounce and click-outside behavior, replacing React\n * state with Lit reactive properties and using direct fetch to RoxyAPI.\n *\n * Attributes:\n * api-key optional. Direct call to roxyapi.com when set.\n * publishable-key optional. Browser-safe pk_* key with allowed_origins.\n * endpoint optional. Override URL (default https://roxyapi.com/api/v2/location/search).\n * placeholder optional. Input placeholder.\n * default-value optional. Pre-filled query.\n */\n@customElement('roxy-location-search')\nexport class RoxyLocationSearch extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t:host {\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t\t.field {\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t\tinput {\n\t\t\t\twidth: 100%;\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);\n\t\t\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\t\t\tfont-family: inherit;\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\ttransition:\n\t\t\t\t\tborder-color var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t\tbox-sizing: border-box;\n\t\t\t}\n\t\t\tinput:focus {\n\t\t\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\t\t\toutline-offset: 2px;\n\t\t\t\tborder-color: var(--roxy-accent-fg, #b45309);\n\t\t\t}\n\t\t\t.spinner {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 12px;\n\t\t\t\ttop: 50%;\n\t\t\t\ttransform: translateY(-50%);\n\t\t\t\twidth: 14px;\n\t\t\t\theight: 14px;\n\t\t\t\tborder: 2px solid var(--roxy-muted, #71717a);\n\t\t\t\tborder-top-color: transparent;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tanimation: roxy-spin 700ms linear infinite;\n\t\t\t}\n\t\t\t@keyframes roxy-spin {\n\t\t\t\tto {\n\t\t\t\t\ttransform: translateY(-50%) rotate(360deg);\n\t\t\t\t}\n\t\t\t}\n\t\t\t@media (prefers-reduced-motion: reduce) {\n\t\t\t\t.spinner {\n\t\t\t\t\tanimation: none;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.results {\n\t\t\t\tposition: absolute;\n\t\t\t\tz-index: 50;\n\t\t\t\ttop: calc(100% + 4px);\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tbox-shadow: var(--roxy-shadow-md);\n\t\t\t\tmax-height: 22rem;\n\t\t\t\toverflow-y: auto;\n\t\t\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1));\n\t\t\t}\n\t\t\t.option {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: baseline;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\twidth: 100%;\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: transparent;\n\t\t\t\tborder: 0;\n\t\t\t\ttext-align: left;\n\t\t\t\tfont-family: inherit;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tcursor: pointer;\n\t\t\t\ttransition: background-color var(--roxy-motion-duration, 200ms);\n\t\t\t}\n\t\t\t.option:hover,\n\t\t\t.option[aria-selected='true'] {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-accent, #f59e0b) 10%, transparent);\n\t\t\t}\n\t\t\t.option .city {\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t}\n\t\t\t.option .where {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tflex-grow: 1;\n\t\t\t}\n\t\t\t.option .tz {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t}\n\t\t\t.empty {\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ type: String, attribute: 'api-key' })\n\tapiKey?: string;\n\n\t@property({ type: String, attribute: 'publishable-key' })\n\tpublishableKey?: string;\n\n\t@property({ type: String })\n\tendpoint = 'https://roxyapi.com/api/v2/location/search';\n\n\t@property({ type: String })\n\tplaceholder = 'Search city';\n\n\t@property({ type: String, attribute: 'default-value' })\n\tdefaultValue = '';\n\n\t@state()\n\tprivate query = '';\n\n\t@state()\n\tprivate results: CityResult[] = [];\n\n\t@state()\n\tprivate isOpen = false;\n\n\t@state()\n\tprivate isLoading = false;\n\n\t@state()\n\tprivate highlight = -1;\n\n\tprivate clickOutsideHandler?: (e: MouseEvent) => void;\n\tprivate abortController?: AbortController;\n\tprivate secretKeyWarned = false;\n\tprivate debouncedFetch = debounce((q: string) => {\n\t\tvoid this.fetchResults(q);\n\t}, 300);\n\n\tconnectedCallback(): void {\n\t\tsuper.connectedCallback();\n\t\tthis.query = this.defaultValue;\n\t\tthis.clickOutsideHandler = (e: MouseEvent) => {\n\t\t\tconst path = e.composedPath();\n\t\t\tif (!path.includes(this)) this.isOpen = false;\n\t\t};\n\t\tdocument.addEventListener('mousedown', this.clickOutsideHandler);\n\t}\n\n\tdisconnectedCallback(): void {\n\t\tsuper.disconnectedCallback();\n\t\tif (this.clickOutsideHandler) {\n\t\t\tdocument.removeEventListener('mousedown', this.clickOutsideHandler);\n\t\t}\n\t\tthis.debouncedFetch.cancel();\n\t\tif (this.abortController) {\n\t\t\tthis.abortController.abort();\n\t\t\tthis.abortController = undefined;\n\t\t}\n\t}\n\n\tprivate warnIfSecretKey() {\n\t\tif (this.secretKeyWarned) return;\n\t\tif (!this.apiKey) return;\n\t\t// Browser-safe publishable keys carry the `pk_` prefix and a server-side\n\t\t// origin allowlist. Anything else (a raw secret key, UUID-style token)\n\t\t// must not ship to the browser.\n\t\tif (this.apiKey.startsWith('pk_')) return;\n\t\tthis.secretKeyWarned = true;\n\t\tconst message =\n\t\t\t'Possible secret key in client-side <roxy-location-search>; use a `pk_` publishable key with origin allowlist instead.';\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.warn(message);\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent('roxy-validation-error', {\n\t\t\t\tdetail: { reason: 'possible-secret-key', message },\n\t\t\t\tbubbles: true,\n\t\t\t\tcomposed: true,\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate async fetchResults(q: string) {\n\t\tthis.warnIfSecretKey();\n\t\t// Abort any in-flight request so a stale response cannot overwrite a\n\t\t// fresher one (debounced typing) or land after disconnect.\n\t\tif (this.abortController) this.abortController.abort();\n\t\tconst controller = new AbortController();\n\t\tthis.abortController = controller;\n\t\tthis.isLoading = true;\n\t\ttry {\n\t\t\tconst url = new URL(this.endpoint);\n\t\t\turl.searchParams.set('q', q);\n\t\t\turl.searchParams.set('limit', '8');\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAccept: 'application/json',\n\t\t\t};\n\t\t\tif (this.apiKey) headers['X-API-Key'] = this.apiKey;\n\t\t\tif (this.publishableKey) headers['X-API-Key'] = this.publishableKey;\n\t\t\tconst res = await fetch(url, { headers, signal: controller.signal });\n\t\t\tif (!res.ok) throw new Error(`HTTP ${res.status}`);\n\t\t\tconst json = (await res.json()) as SearchCitiesResponse;\n\t\t\tif (controller.signal.aborted) return;\n\t\t\tthis.results = json.cities ?? [];\n\t\t\tthis.isOpen = this.results.length > 0;\n\t\t\tthis.highlight = this.results.length > 0 ? 0 : -1;\n\t\t} catch (err) {\n\t\t\tif ((err as { name?: string })?.name === 'AbortError') return;\n\t\t\tthis.results = [];\n\t\t\tthis.isOpen = false;\n\t\t} finally {\n\t\t\tif (this.abortController === controller) {\n\t\t\t\tthis.abortController = undefined;\n\t\t\t}\n\t\t\tif (!controller.signal.aborted) this.isLoading = false;\n\t\t}\n\t}\n\n\tprivate onInput = (e: Event) => {\n\t\tconst value = (e.target as HTMLInputElement).value;\n\t\tthis.query = value;\n\t\tif (value.length < 2) {\n\t\t\tthis.results = [];\n\t\t\tthis.isOpen = false;\n\t\t\tthis.highlight = -1;\n\t\t\treturn;\n\t\t}\n\t\tthis.debouncedFetch(value);\n\t};\n\n\tprivate select(city: CityResult) {\n\t\tthis.query = `${city.city}${city.province ? `, ${city.province}` : ''}, ${city.country}`;\n\t\tthis.isOpen = false;\n\t\tthis.results = [];\n\t\tthis.dispatchEvent(\n\t\t\tnew CustomEvent('roxy-location-select', {\n\t\t\t\tdetail: city,\n\t\t\t\tbubbles: true,\n\t\t\t\tcomposed: true,\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate onKeyDown = (e: KeyboardEvent) => {\n\t\tif (!this.isOpen || this.results.length === 0) {\n\t\t\tif (e.key === 'ArrowDown' && this.query.length >= 2) {\n\t\t\t\tvoid this.fetchResults(this.query);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (e.key === 'ArrowDown') {\n\t\t\te.preventDefault();\n\t\t\tthis.highlight = (this.highlight + 1) % this.results.length;\n\t\t} else if (e.key === 'ArrowUp') {\n\t\t\te.preventDefault();\n\t\t\tthis.highlight =\n\t\t\t\t(this.highlight - 1 + this.results.length) % this.results.length;\n\t\t} else if (e.key === 'Enter') {\n\t\t\te.preventDefault();\n\t\t\tconst target = this.results[this.highlight] ?? this.results[0];\n\t\t\tif (target) this.select(target);\n\t\t} else if (e.key === 'Escape') {\n\t\t\tthis.isOpen = false;\n\t\t}\n\t};\n\n\trender() {\n\t\treturn html`<div class=\"field\">\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\trole=\"combobox\"\n\t\t\t\taria-expanded=${this.isOpen ? 'true' : 'false'}\n\t\t\t\taria-controls=\"roxy-location-listbox\"\n\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tplaceholder=${this.placeholder}\n\t\t\t\t.value=${this.query}\n\t\t\t\t@input=${this.onInput}\n\t\t\t\t@keydown=${this.onKeyDown}\n\t\t\t\t@focus=${() => {\n\t\t\t\t\tif (this.results.length > 0) this.isOpen = true;\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t${this.isLoading ? html`<span class=\"spinner\" role=\"status\" aria-label=\"Loading\"></span>` : nothing}\n\t\t\t${\n\t\t\t\tthis.isOpen\n\t\t\t\t\t? html`<ul\n\t\t\t\t\t\tid=\"roxy-location-listbox\"\n\t\t\t\t\t\tclass=\"results\"\n\t\t\t\t\t\trole=\"listbox\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tthis.results.length === 0\n\t\t\t\t\t\t\t\t? html`<li class=\"empty\" role=\"status\">No cities found</li>`\n\t\t\t\t\t\t\t\t: this.results.map(\n\t\t\t\t\t\t\t\t\t\t(city, idx) => html`<li role=\"presentation\">\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tclass=\"option\"\n\t\t\t\t\t\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\t\t\t\t\t\taria-selected=${this.highlight === idx ? 'true' : 'false'}\n\t\t\t\t\t\t\t\t\t\t\t@click=${() => this.select(city)}\n\t\t\t\t\t\t\t\t\t\t\t@mouseenter=${() => {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.highlight = idx;\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"city\">${city.city}</span>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"where\"\n\t\t\t\t\t\t\t\t\t\t\t\t>${city.province ? html`${city.province}, ` : ''}${city.country}</span\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"tz\"\n\t\t\t\t\t\t\t\t\t\t\t\t>UTC${city.utcOffset >= 0 ? '+' : ''}${city.utcOffset}</span\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</li>`,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t</ul>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-location-search': RoxyLocationSearch;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Lightweight debounce for input handlers. Used by location search.\n *\n * The returned function exposes a `.cancel()` method so callers can clear a\n * pending invocation when the host element disconnects, preventing the timer\n * from firing on a detached node and mutating reactive state after teardown.\n */\nexport interface Debounced<F extends (...args: never[]) => unknown> {\n\t(...args: Parameters<F>): void;\n\tcancel: () => void;\n}\n\nexport function debounce<F extends (...args: never[]) => unknown>(\n\tfn: F,\n\twait: number,\n): Debounced<F> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst debounced = ((...args: Parameters<F>) => {\n\t\tif (timer) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = undefined;\n\t\t\tfn(...args);\n\t\t}, wait);\n\t}) as Debounced<F>;\n\tdebounced.cancel = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\t};\n\treturn debounced;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["css", "css"]
|
|
7
7
|
}
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
import { LitElement } from 'lit';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
phase?: string;
|
|
5
|
-
illumination?: number;
|
|
6
|
-
age?: number;
|
|
7
|
-
sign?: string;
|
|
8
|
-
degree?: number;
|
|
9
|
-
distance?: number;
|
|
10
|
-
meaning?: {
|
|
11
|
-
name?: string;
|
|
12
|
-
symbol?: string;
|
|
13
|
-
description?: string;
|
|
14
|
-
keywords?: string[];
|
|
15
|
-
};
|
|
16
|
-
month?: string;
|
|
17
|
-
year?: number;
|
|
18
|
-
phases?: Array<MoonPhaseData>;
|
|
19
|
-
upcoming?: Array<MoonPhaseData>;
|
|
20
|
-
}
|
|
1
|
+
import { LitElement, nothing } from 'lit';
|
|
2
|
+
import type { GetCurrentMoonPhaseResponse, GetMoonCalendarResponse, GetUpcomingMoonPhasesResponse } from '../types/index.js';
|
|
3
|
+
type MoonPhaseData = GetCurrentMoonPhaseResponse | GetUpcomingMoonPhasesResponse | GetMoonCalendarResponse;
|
|
21
4
|
/**
|
|
22
5
|
* Moon phase card. Renders /astrology/moon-phase/{current,upcoming,calendar/...}.
|
|
23
6
|
*/
|
|
@@ -25,7 +8,7 @@ export declare class RoxyMoonPhase extends LitElement {
|
|
|
25
8
|
static styles: import("lit").CSSResult[];
|
|
26
9
|
data: MoonPhaseData | null;
|
|
27
10
|
mode: 'current' | 'upcoming' | 'calendar';
|
|
28
|
-
render(): import("lit").TemplateResult<1
|
|
11
|
+
render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
29
12
|
private renderSingle;
|
|
30
13
|
private renderListItem;
|
|
31
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"moon-phase.d.ts","sourceRoot":"","sources":["../../src/components/moon-phase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,
|
|
1
|
+
{"version":3,"file":"moon-phase.d.ts","sourceRoot":"","sources":["../../src/components/moon-phase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EACX,2BAA2B,EAC3B,uBAAuB,EACvB,6BAA6B,EAC7B,MAAM,mBAAmB,CAAC;AAI3B,KAAK,aAAa,GACf,2BAA2B,GAC3B,6BAA6B,GAC7B,uBAAuB,CAAC;AAK3B;;GAEG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC5C,MAAM,CAAC,MAAM,4BAqFX;IAGF,IAAI,EAAE,aAAa,GAAG,IAAI,CAAQ;IAGlC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAa;IAEtD,MAAM;IAuBN,OAAO,CAAC,YAAY;IA2DpB,OAAO,CAAC,cAAc;CAQtB;AAYD,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,iBAAiB,EAAE,aAAa,CAAC;KACjC;CACD"}
|
|
@@ -111,6 +111,12 @@ var baseStyles = css`
|
|
|
111
111
|
}
|
|
112
112
|
`;
|
|
113
113
|
|
|
114
|
+
// packages/ui/src/utils/format.ts
|
|
115
|
+
function formatNumber(value, dp = 1) {
|
|
116
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "";
|
|
117
|
+
return value.toFixed(dp).replace(/\.?0+$/, "");
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
// packages/ui/src/components/moon-phase.ts
|
|
115
121
|
var RoxyMoonPhase = class extends LitElement {
|
|
116
122
|
constructor() {
|
|
@@ -122,18 +128,21 @@ var RoxyMoonPhase = class extends LitElement {
|
|
|
122
128
|
const d = this.data;
|
|
123
129
|
if (!d)
|
|
124
130
|
return html`<div class="roxy-empty" role="status">No moon phase data</div>`;
|
|
125
|
-
const list = d.phases
|
|
131
|
+
const list = "phases" in d ? d.phases : "calendar" in d ? d.calendar : [];
|
|
126
132
|
if (this.mode !== "current" && list.length > 0) {
|
|
133
|
+
const month = "month" in d ? d.month : void 0;
|
|
134
|
+
const year = "year" in d ? d.year : void 0;
|
|
127
135
|
return html`<article
|
|
128
136
|
class="card"
|
|
129
137
|
aria-label="Moon phase calendar"
|
|
130
138
|
>
|
|
131
|
-
<h2 class="label">${
|
|
139
|
+
<h2 class="label">${month ?? "Moon phases"} ${year ?? ""}</h2>
|
|
132
140
|
<div class="list" role="list">
|
|
133
141
|
${list.map((phase) => this.renderListItem(phase))}
|
|
134
142
|
</div>
|
|
135
143
|
</article>`;
|
|
136
144
|
}
|
|
145
|
+
if (!("phase" in d)) return nothing;
|
|
137
146
|
return this.renderSingle(d);
|
|
138
147
|
}
|
|
139
148
|
renderSingle(d) {
|
|
@@ -149,11 +158,11 @@ var RoxyMoonPhase = class extends LitElement {
|
|
|
149
158
|
<div class="stats">
|
|
150
159
|
${typeof d.illumination === "number" ? html`<div>
|
|
151
160
|
<span>Illumination</span>
|
|
152
|
-
<strong>${(d.illumination
|
|
161
|
+
<strong>${formatIllumination(d.illumination)}</strong>
|
|
153
162
|
</div>` : nothing}
|
|
154
163
|
${typeof d.age === "number" ? html`<div>
|
|
155
164
|
<span>Age</span>
|
|
156
|
-
<strong>${d.age
|
|
165
|
+
<strong>${formatNumber(d.age, 1)} days</strong>
|
|
157
166
|
</div>` : nothing}
|
|
158
167
|
${d.sign ? html`<div>
|
|
159
168
|
<span>Sign</span>
|
|
@@ -278,6 +287,10 @@ function phaseEmoji(phase) {
|
|
|
278
287
|
if (!phase) return "\u{1F319}";
|
|
279
288
|
return MOON_PHASE_EMOJI[phase.toLowerCase()] ?? "\u{1F319}";
|
|
280
289
|
}
|
|
290
|
+
function formatIllumination(v) {
|
|
291
|
+
const pct = v <= 1 ? v * 100 : v;
|
|
292
|
+
return `${Math.round(pct)}%`;
|
|
293
|
+
}
|
|
281
294
|
export {
|
|
282
295
|
RoxyMoonPhase
|
|
283
296
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/components/moon-phase.ts", "../../src/tokens/index.ts", "../../src/utils/base-styles.ts"],
|
|
4
|
-
"sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { MOON_PHASE_EMOJI } from '../tokens/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\n\ninterface MoonPhaseData {\n\tdate?: string;\n\tphase?: string;\n\tillumination?: number;\n\tage?: number;\n\tsign?: string;\n\tdegree?: number;\n\tdistance?: number;\n\tmeaning?: {\n\t\tname?: string;\n\t\tsymbol?: string;\n\t\tdescription?: string;\n\t\tkeywords?: string[];\n\t};\n\tmonth?: string;\n\tyear?: number;\n\tphases?: Array<MoonPhaseData>;\n\tupcoming?: Array<MoonPhaseData>;\n}\n\n/**\n * Moon phase card. Renders /astrology/moon-phase/{current,upcoming,calendar/...}.\n */\n@customElement('roxy-moon-phase')\nexport class RoxyMoonPhase extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.card {\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\n\t\t\t.hero {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.emoji {\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\t.label {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\ttext-transform: capitalize;\n\t\t\t}\n\t\t\t.date {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.stats {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-secondary, #475569);\n\t\t\t}\n\t\t\t.stats div span:first-child {\n\t\t\t\tdisplay: block;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.stats strong {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t}\n\n\t\t\t.meaning {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t}\n\t\t\t.keywords {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-xs, 0.25rem);\n\t\t\t\tmargin-top: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.keywords span {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);\n\t\t\t\tpadding: 2px 8px;\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t}\n\n\t\t\t.list {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.list-item {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 2.5rem 1fr auto;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tborder-bottom: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) 0;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.list-item:last-child {\n\t\t\t\tborder-bottom: none;\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: MoonPhaseData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tmode: 'current' | 'upcoming' | 'calendar' = 'current';\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No moon phase data</div>`;\n\t\tconst list = d.phases ?? d.upcoming ?? [];\n\t\tif (this.mode !== 'current' && list.length > 0) {\n\t\t\treturn html`<article\n\t\t\t\tclass=\"card\"\n\t\t\t\taria-label=\"Moon phase calendar\"\n\t\t\t>\n\t\t\t\t<h2 class=\"label\">${d.month ?? 'Moon phases'} ${d.year ?? ''}</h2>\n\t\t\t\t<div class=\"list\" role=\"list\">\n\t\t\t\t\t${list.map((phase) => this.renderListItem(phase))}\n\t\t\t\t</div>\n\t\t\t</article>`;\n\t\t}\n\t\treturn this.renderSingle(d);\n\t}\n\n\tprivate renderSingle(d: MoonPhaseData) {\n\t\tconst emoji = phaseEmoji(d.phase);\n\t\treturn html`<article class=\"card\" aria-label=\"Current moon phase\">\n\t\t\t<div class=\"hero\">\n\t\t\t\t<span class=\"emoji\" aria-hidden=\"true\">${emoji}</span>\n\t\t\t\t<div>\n\t\t\t\t\t<h2 class=\"label\">${d.phase ?? 'Moon'}</h2>\n\t\t\t\t\t${d.date ? html`<div class=\"date\">${d.date}</div>` : nothing}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"stats\">\n\t\t\t\t${\n\t\t\t\t\ttypeof d.illumination === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Illumination</span>\n\t\t\t\t\t\t\t<strong>${(d.illumination * 100).toFixed(0)}%</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\ttypeof d.age === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Age</span>\n\t\t\t\t\t\t\t<strong>${d.age.toFixed(1)} days</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\td.sign\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Sign</span>\n\t\t\t\t\t\t\t<strong>${d.sign}</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\ttypeof d.distance === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Distance</span>\n\t\t\t\t\t\t\t<strong>${(d.distance / 1000).toFixed(0)}k km</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t${\n\t\t\t\td.meaning?.description\n\t\t\t\t\t? html`<p class=\"meaning\">${d.meaning.description}</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\td.meaning?.keywords?.length\n\t\t\t\t\t? html`<div class=\"keywords\">\n\t\t\t\t\t\t${d.meaning.keywords.map((k) => html`<span>${k}</span>`)}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</article>`;\n\t}\n\n\tprivate renderListItem(p: MoonPhaseData) {\n\t\tconst emoji = phaseEmoji(p.phase);\n\t\treturn html`<div class=\"list-item\" role=\"listitem\">\n\t\t\t<span aria-hidden=\"true\">${emoji}</span>\n\t\t\t<span>${p.phase}</span>\n\t\t\t<span>${p.date ?? ''}</span>\n\t\t</div>`;\n\t}\n}\n\nfunction phaseEmoji(phase: string | undefined): string {\n\tif (!phase) return '\uD83C\uDF19';\n\treturn MOON_PHASE_EMOJI[phase.toLowerCase()] ?? '\uD83C\uDF19';\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-moon-phase': RoxyMoonPhase;\n\t}\n}\n", "/**\n * Symbol constants used across components. Single source of truth so chart\n * wheels, card headers, hexagram displays, and panchang tables stay visually\n * consistent.\n */\n\nexport const PLANET_GLYPH: Record<string, string> = {\n\tSun: '\u2609',\n\tMoon: '\u263D',\n\tMercury: '\u263F',\n\tVenus: '\u2640',\n\tEarth: '\u2641',\n\tMars: '\u2642',\n\tJupiter: '\u2643',\n\tSaturn: '\u2644',\n\tUranus: '\u2645',\n\tNeptune: '\u2646',\n\tPluto: '\u2647',\n\tRahu: '\u260A',\n\tKetu: '\u260B',\n\tAscendant: 'Asc',\n\tLagna: 'La',\n\tNorthNode: '\u260A',\n\tSouthNode: '\u260B',\n};\n\nexport const PLANET_ABBR: Record<string, string> = {\n\tSun: 'Su',\n\tMoon: 'Mo',\n\tMercury: 'Me',\n\tVenus: 'Ve',\n\tMars: 'Ma',\n\tJupiter: 'Ju',\n\tSaturn: 'Sa',\n\tUranus: 'Ur',\n\tNeptune: 'Ne',\n\tPluto: 'Pl',\n\tRahu: 'Ra',\n\tKetu: 'Ke',\n\tAscendant: 'Asc',\n\tLagna: 'La',\n};\n\nexport const SIGN_GLYPH: Record<string, string> = {\n\tAries: '\u2648',\n\tTaurus: '\u2649',\n\tGemini: '\u264A',\n\tCancer: '\u264B',\n\tLeo: '\u264C',\n\tVirgo: '\u264D',\n\tLibra: '\u264E',\n\tScorpio: '\u264F',\n\tSagittarius: '\u2650',\n\tCapricorn: '\u2651',\n\tAquarius: '\u2652',\n\tPisces: '\u2653',\n};\n\nexport const SIGN_ABBR: Record<string, string> = {\n\tAries: 'Ar',\n\tTaurus: 'Ta',\n\tGemini: 'Ge',\n\tCancer: 'Cn',\n\tLeo: 'Le',\n\tVirgo: 'Vi',\n\tLibra: 'Li',\n\tScorpio: 'Sc',\n\tSagittarius: 'Sg',\n\tCapricorn: 'Cp',\n\tAquarius: 'Aq',\n\tPisces: 'Pi',\n};\n\nexport const SIGNS_ORDER = [\n\t'Aries',\n\t'Taurus',\n\t'Gemini',\n\t'Cancer',\n\t'Leo',\n\t'Virgo',\n\t'Libra',\n\t'Scorpio',\n\t'Sagittarius',\n\t'Capricorn',\n\t'Aquarius',\n\t'Pisces',\n] as const;\n\n/** Aspect symbols. Used by synastry and natal chart aspect tables. */\nexport const ASPECT_SYMBOL: Record<string, string> = {\n\tconjunction: '\u260C',\n\topposition: '\u260D',\n\ttrine: '\u25B3',\n\tsquare: '\u25A1',\n\tsextile: '\u2731',\n\tquincunx: '\u22BB',\n\tsemisextile: '\u22BC',\n};\n\n/** Trigrams used by I Ching hexagrams. Eight trigrams compose 64 hexagrams. */\nexport const TRIGRAM_GLYPH: Record<string, string> = {\n\theaven: '\u2630',\n\tlake: '\u2631',\n\tfire: '\u2632',\n\tthunder: '\u2633',\n\twind: '\u2634',\n\twater: '\u2635',\n\tmountain: '\u2636',\n\tearth: '\u2637',\n\tHeaven: '\u2630',\n\tLake: '\u2631',\n\tFire: '\u2632',\n\tThunder: '\u2633',\n\tWind: '\u2634',\n\tWater: '\u2635',\n\tMountain: '\u2636',\n\tEarth: '\u2637',\n};\n\n/** Moon phase emoji set. Used by moon phase card. */\nexport const MOON_PHASE_EMOJI: Record<string, string> = {\n\t'new moon': '\uD83C\uDF11',\n\t'waxing crescent': '\uD83C\uDF12',\n\t'first quarter': '\uD83C\uDF13',\n\t'waxing gibbous': '\uD83C\uDF14',\n\t'full moon': '\uD83C\uDF15',\n\t'waning gibbous': '\uD83C\uDF16',\n\t'last quarter': '\uD83C\uDF17',\n\t'waning crescent': '\uD83C\uDF18',\n};\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;AAAA,SAAS,OAAAA,MAAK,MAAM,YAAY,eAAe;AAC/C,SAAS,eAAe,gBAAgB;;;
|
|
3
|
+
"sources": ["../../src/components/moon-phase.ts", "../../src/tokens/index.ts", "../../src/utils/base-styles.ts", "../../src/utils/format.ts"],
|
|
4
|
+
"sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { MOON_PHASE_EMOJI } from '../tokens/index.js';\nimport type {\n\tGetCurrentMoonPhaseResponse,\n\tGetMoonCalendarResponse,\n\tGetUpcomingMoonPhasesResponse,\n} from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { formatNumber } from '../utils/format.js';\n\ntype MoonPhaseData =\n\t| GetCurrentMoonPhaseResponse\n\t| GetUpcomingMoonPhasesResponse\n\t| GetMoonCalendarResponse;\ntype MoonListEntry =\n\t| GetUpcomingMoonPhasesResponse['phases'][number]\n\t| GetMoonCalendarResponse['calendar'][number];\n\n/**\n * Moon phase card. Renders /astrology/moon-phase/{current,upcoming,calendar/...}.\n */\n@customElement('roxy-moon-phase')\nexport class RoxyMoonPhase extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.card {\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\t\t\tbox-shadow: var(--roxy-shadow-sm);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\n\t\t\t.hero {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.emoji {\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\t.label {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\ttext-transform: capitalize;\n\t\t\t}\n\t\t\t.date {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\n\t\t\t.stats {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-secondary, #475569);\n\t\t\t}\n\t\t\t.stats div span:first-child {\n\t\t\t\tdisplay: block;\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.06em;\n\t\t\t}\n\t\t\t.stats strong {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t}\n\n\t\t\t.meaning {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t}\n\t\t\t.keywords {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-xs, 0.25rem);\n\t\t\t\tmargin-top: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.keywords span {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);\n\t\t\t\tpadding: 2px 8px;\n\t\t\t\tborder-radius: var(--roxy-radius-full, 9999px);\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t}\n\n\t\t\t.list {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.list-item {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: 2.5rem 1fr auto;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\talign-items: center;\n\t\t\t\tborder-bottom: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tpadding: var(--roxy-space-sm, 0.5rem) 0;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\t.list-item:last-child {\n\t\t\t\tborder-bottom: none;\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: MoonPhaseData | null = null;\n\n\t@property({ type: String, reflect: true })\n\tmode: 'current' | 'upcoming' | 'calendar' = 'current';\n\n\trender() {\n\t\tconst d = this.data;\n\t\tif (!d)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No moon phase data</div>`;\n\t\tconst list: MoonListEntry[] =\n\t\t\t'phases' in d ? d.phases : 'calendar' in d ? d.calendar : [];\n\t\tif (this.mode !== 'current' && list.length > 0) {\n\t\t\tconst month = 'month' in d ? d.month : undefined;\n\t\t\tconst year = 'year' in d ? d.year : undefined;\n\t\t\treturn html`<article\n\t\t\t\tclass=\"card\"\n\t\t\t\taria-label=\"Moon phase calendar\"\n\t\t\t>\n\t\t\t\t<h2 class=\"label\">${month ?? 'Moon phases'} ${year ?? ''}</h2>\n\t\t\t\t<div class=\"list\" role=\"list\">\n\t\t\t\t\t${list.map((phase) => this.renderListItem(phase))}\n\t\t\t\t</div>\n\t\t\t</article>`;\n\t\t}\n\t\tif (!('phase' in d)) return nothing;\n\t\treturn this.renderSingle(d);\n\t}\n\n\tprivate renderSingle(d: GetCurrentMoonPhaseResponse) {\n\t\tconst emoji = phaseEmoji(d.phase);\n\t\treturn html`<article class=\"card\" aria-label=\"Current moon phase\">\n\t\t\t<div class=\"hero\">\n\t\t\t\t<span class=\"emoji\" aria-hidden=\"true\">${emoji}</span>\n\t\t\t\t<div>\n\t\t\t\t\t<h2 class=\"label\">${d.phase ?? 'Moon'}</h2>\n\t\t\t\t\t${d.date ? html`<div class=\"date\">${d.date}</div>` : nothing}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"stats\">\n\t\t\t\t${\n\t\t\t\t\ttypeof d.illumination === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Illumination</span>\n\t\t\t\t\t\t\t<strong>${formatIllumination(d.illumination)}</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\ttypeof d.age === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Age</span>\n\t\t\t\t\t\t\t<strong>${formatNumber(d.age, 1)} days</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\td.sign\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Sign</span>\n\t\t\t\t\t\t\t<strong>${d.sign}</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\ttypeof d.distance === 'number'\n\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t<span>Distance</span>\n\t\t\t\t\t\t\t<strong>${(d.distance / 1000).toFixed(0)}k km</strong>\n\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t: nothing\n\t\t\t\t}\n\t\t\t</div>\n\t\t\t${\n\t\t\t\td.meaning?.description\n\t\t\t\t\t? html`<p class=\"meaning\">${d.meaning.description}</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\td.meaning?.keywords?.length\n\t\t\t\t\t? html`<div class=\"keywords\">\n\t\t\t\t\t\t${d.meaning.keywords.map((k) => html`<span>${k}</span>`)}\n\t\t\t\t\t</div>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</article>`;\n\t}\n\n\tprivate renderListItem(p: MoonListEntry) {\n\t\tconst emoji = phaseEmoji(p.phase);\n\t\treturn html`<div class=\"list-item\" role=\"listitem\">\n\t\t\t<span aria-hidden=\"true\">${emoji}</span>\n\t\t\t<span>${p.phase}</span>\n\t\t\t<span>${p.date ?? ''}</span>\n\t\t</div>`;\n\t}\n}\n\nfunction phaseEmoji(phase: string | undefined): string {\n\tif (!phase) return '\uD83C\uDF19';\n\treturn MOON_PHASE_EMOJI[phase.toLowerCase()] ?? '\uD83C\uDF19';\n}\n\nfunction formatIllumination(v: number): string {\n\tconst pct = v <= 1 ? v * 100 : v;\n\treturn `${Math.round(pct)}%`;\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-moon-phase': RoxyMoonPhase;\n\t}\n}\n", "/**\n * Symbol constants used across components. Single source of truth so chart\n * wheels, card headers, hexagram displays, and panchang tables stay visually\n * consistent.\n */\n\nexport const PLANET_GLYPH: Record<string, string> = {\n\tSun: '\u2609',\n\tMoon: '\u263D',\n\tMercury: '\u263F',\n\tVenus: '\u2640',\n\tEarth: '\u2641',\n\tMars: '\u2642',\n\tJupiter: '\u2643',\n\tSaturn: '\u2644',\n\tUranus: '\u2645',\n\tNeptune: '\u2646',\n\tPluto: '\u2647',\n\tRahu: '\u260A',\n\tKetu: '\u260B',\n\tAscendant: 'Asc',\n\tLagna: 'La',\n\tNorthNode: '\u260A',\n\tSouthNode: '\u260B',\n\t'North node': '\u260A',\n\t'South node': '\u260B',\n\tChiron: '\u26B7',\n\tLilith: '\u26B8',\n\t'Black moon lilith': '\u26B8',\n};\n\nexport const PLANET_ABBR: Record<string, string> = {\n\tSun: 'Su',\n\tMoon: 'Mo',\n\tMercury: 'Me',\n\tVenus: 'Ve',\n\tMars: 'Ma',\n\tJupiter: 'Ju',\n\tSaturn: 'Sa',\n\tUranus: 'Ur',\n\tNeptune: 'Ne',\n\tPluto: 'Pl',\n\tRahu: 'Ra',\n\tKetu: 'Ke',\n\tAscendant: 'Asc',\n\tLagna: 'La',\n};\n\nexport const SIGN_GLYPH: Record<string, string> = {\n\tAries: '\u2648',\n\tTaurus: '\u2649',\n\tGemini: '\u264A',\n\tCancer: '\u264B',\n\tLeo: '\u264C',\n\tVirgo: '\u264D',\n\tLibra: '\u264E',\n\tScorpio: '\u264F',\n\tSagittarius: '\u2650',\n\tCapricorn: '\u2651',\n\tAquarius: '\u2652',\n\tPisces: '\u2653',\n};\n\nexport const SIGN_ABBR: Record<string, string> = {\n\tAries: 'Ar',\n\tTaurus: 'Ta',\n\tGemini: 'Ge',\n\tCancer: 'Cn',\n\tLeo: 'Le',\n\tVirgo: 'Vi',\n\tLibra: 'Li',\n\tScorpio: 'Sc',\n\tSagittarius: 'Sg',\n\tCapricorn: 'Cp',\n\tAquarius: 'Aq',\n\tPisces: 'Pi',\n};\n\nexport const SIGNS_ORDER = [\n\t'Aries',\n\t'Taurus',\n\t'Gemini',\n\t'Cancer',\n\t'Leo',\n\t'Virgo',\n\t'Libra',\n\t'Scorpio',\n\t'Sagittarius',\n\t'Capricorn',\n\t'Aquarius',\n\t'Pisces',\n] as const;\n\n/** Aspect symbols. Used by synastry and natal chart aspect tables. */\nexport const ASPECT_SYMBOL: Record<string, string> = {\n\tconjunction: '\u260C',\n\topposition: '\u260D',\n\ttrine: '\u25B3',\n\tsquare: '\u25A1',\n\tsextile: '\u2731',\n\tquincunx: '\u22BB',\n\tsemisextile: '\u22BC',\n};\n\n/** Trigrams used by I Ching hexagrams. Eight trigrams compose 64 hexagrams. */\nexport const TRIGRAM_GLYPH: Record<string, string> = {\n\theaven: '\u2630',\n\tlake: '\u2631',\n\tfire: '\u2632',\n\tthunder: '\u2633',\n\twind: '\u2634',\n\twater: '\u2635',\n\tmountain: '\u2636',\n\tearth: '\u2637',\n\tHeaven: '\u2630',\n\tLake: '\u2631',\n\tFire: '\u2632',\n\tThunder: '\u2633',\n\tWind: '\u2634',\n\tWater: '\u2635',\n\tMountain: '\u2636',\n\tEarth: '\u2637',\n};\n\n/** Moon phase emoji set. Used by moon phase card. */\nexport const MOON_PHASE_EMOJI: Record<string, string> = {\n\t'new moon': '\uD83C\uDF11',\n\t'waxing crescent': '\uD83C\uDF12',\n\t'first quarter': '\uD83C\uDF13',\n\t'waxing gibbous': '\uD83C\uDF14',\n\t'full moon': '\uD83C\uDF15',\n\t'waning gibbous': '\uD83C\uDF16',\n\t'last quarter': '\uD83C\uDF17',\n\t'waning crescent': '\uD83C\uDF18',\n};\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Display formatters for ISO timestamps and floats coming back from the API.\n * Every helper returns \"\" for nullish or unparseable input so it falls out of\n * template literals cleanly.\n */\n\nexport function formatTime(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tif (/^\\d{4}-\\d{2}-\\d{2}$/.test(input)) return '';\n\tconst bareTime = /^\\d{2}:\\d{2}(:\\d{2})?$/.test(input);\n\tconst iso = bareTime ? `1970-01-01T${input}` : input;\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleTimeString(undefined, {\n\t\thour: 'numeric',\n\t\tminute: '2-digit',\n\t\thour12: true,\n\t});\n}\n\nexport function formatDate(input: unknown): string {\n\tif (typeof input !== 'string' || input.length === 0) return '';\n\tconst d = new Date(\n\t\t/^\\d{4}-\\d{2}-\\d{2}$/.test(input) ? `${input}T00:00:00` : input,\n\t);\n\tif (Number.isNaN(d.getTime())) return input;\n\treturn d.toLocaleDateString(undefined, {\n\t\tmonth: 'short',\n\t\tday: 'numeric',\n\t\tyear: 'numeric',\n\t});\n}\n\nexport function formatTimeRange(\n\tt: { start?: string; end?: string } | undefined,\n): string {\n\tif (!t) return '';\n\tconst start = formatTime(t.start);\n\tconst end = formatTime(t.end);\n\tif (start && end) return `${start} - ${end}`;\n\treturn start || end || '';\n}\n\nexport function formatNumber(value: unknown, dp = 1): string {\n\tif (typeof value !== 'number' || !Number.isFinite(value)) return '';\n\treturn value.toFixed(dp).replace(/\\.?0+$/, '');\n}\n\nexport function formatPercent(value: unknown, dp = 1): string {\n\tconst n = formatNumber(value, dp);\n\treturn n ? `${n}%` : '';\n}\n\nexport function formatLongitude(value: unknown): string {\n\tif (typeof value !== 'number' || !Number.isFinite(value)) return '';\n\treturn `${formatNumber(value, 2)}\u00B0`;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["css", "css"]
|
|
7
7
|
}
|
|
@@ -1,55 +1,20 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import {
|
|
3
|
-
interface PlanetEntry {
|
|
4
|
-
name?: string;
|
|
5
|
-
planet?: string;
|
|
6
|
-
longitude?: number;
|
|
7
|
-
degree?: number;
|
|
8
|
-
sign?: string;
|
|
9
|
-
house?: number;
|
|
10
|
-
retrograde?: boolean;
|
|
11
|
-
isRetrograde?: boolean;
|
|
12
|
-
}
|
|
13
|
-
interface AspectEntry {
|
|
14
|
-
planet1?: string;
|
|
15
|
-
planet2?: string;
|
|
16
|
-
aspect?: string;
|
|
17
|
-
orb?: number;
|
|
18
|
-
}
|
|
19
|
-
interface HouseEntry {
|
|
20
|
-
house?: number;
|
|
21
|
-
number?: number;
|
|
22
|
-
cusp?: number;
|
|
23
|
-
sign?: string;
|
|
24
|
-
}
|
|
25
|
-
interface NatalChartData {
|
|
26
|
-
planets?: PlanetEntry[] | Record<string, PlanetEntry>;
|
|
27
|
-
houses?: HouseEntry[];
|
|
28
|
-
aspects?: AspectEntry[];
|
|
29
|
-
ascendant?: number | {
|
|
30
|
-
longitude?: number;
|
|
31
|
-
sign?: string;
|
|
32
|
-
};
|
|
33
|
-
midheaven?: number | {
|
|
34
|
-
longitude?: number;
|
|
35
|
-
sign?: string;
|
|
36
|
-
};
|
|
37
|
-
birthDetails?: {
|
|
38
|
-
date?: string;
|
|
39
|
-
time?: string;
|
|
40
|
-
location?: string;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
2
|
+
import type { NatalChartResponse } from '../types/index.js';
|
|
43
3
|
/**
|
|
44
4
|
* Western natal chart wheel. Renders the 12 zodiac signs, 12 houses, planet
|
|
45
5
|
* markers, and aspect lines from a /astrology/natal-chart response.
|
|
46
6
|
*/
|
|
47
7
|
export declare class RoxyNatalChart extends LitElement {
|
|
48
8
|
static styles: import("lit").CSSResult[];
|
|
49
|
-
data:
|
|
9
|
+
data: NatalChartResponse | null;
|
|
50
10
|
houseSystem: 'placidus' | 'whole-sign' | 'equal' | 'koch';
|
|
51
11
|
private getPlanets;
|
|
12
|
+
private getAscendant;
|
|
13
|
+
private getMidheaven;
|
|
14
|
+
private toAngle;
|
|
52
15
|
render(): import("lit").TemplateResult<1>;
|
|
16
|
+
private renderAngles;
|
|
17
|
+
private renderAngleMark;
|
|
53
18
|
private renderSpokes;
|
|
54
19
|
private renderSigns;
|
|
55
20
|
private renderHouseNumbers;
|
|
@@ -61,5 +26,4 @@ declare global {
|
|
|
61
26
|
'roxy-natal-chart': RoxyNatalChart;
|
|
62
27
|
}
|
|
63
28
|
}
|
|
64
|
-
export { longitudeToSignPosition };
|
|
65
29
|
//# sourceMappingURL=natal-chart.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"natal-chart.d.ts","sourceRoot":"","sources":["../../src/components/natal-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAgB,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"natal-chart.d.ts","sourceRoot":"","sources":["../../src/components/natal-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAgB,MAAM,KAAK,CAAC;AAG1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAiB5D;;;GAGG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC7C,MAAM,CAAC,MAAM,4BAsGX;IAGF,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAQ;IAGvC,WAAW,EAAE,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAc;IAEvE,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,OAAO;IAIf,MAAM;IA+DN,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;CA6BrB;AAmBD,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,kBAAkB,EAAE,cAAc,CAAC;KACnC;CACD"}
|