@roxyapi/ui 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +9 -3
  3. package/dist/cdn/components/ashtakavarga-grid.js +349 -0
  4. package/dist/cdn/components/ashtakavarga-grid.js.map +7 -0
  5. package/dist/cdn/components/choghadiya-grid.js +239 -0
  6. package/dist/cdn/components/choghadiya-grid.js.map +7 -0
  7. package/dist/cdn/components/compatibility-card.js +6 -6
  8. package/dist/cdn/components/compatibility-card.js.map +1 -1
  9. package/dist/cdn/components/dasha-timeline.js +4 -4
  10. package/dist/cdn/components/dasha-timeline.js.map +1 -1
  11. package/dist/cdn/components/data.js +9 -9
  12. package/dist/cdn/components/data.js.map +4 -4
  13. package/dist/cdn/components/divisional-chart.js +279 -0
  14. package/dist/cdn/components/divisional-chart.js.map +7 -0
  15. package/dist/cdn/components/dosha-card.js +39 -39
  16. package/dist/cdn/components/dosha-card.js.map +3 -3
  17. package/dist/cdn/components/endpoint-form.js +8 -8
  18. package/dist/cdn/components/endpoint-form.js.map +4 -4
  19. package/dist/cdn/components/guna-milan.js +64 -22
  20. package/dist/cdn/components/guna-milan.js.map +3 -3
  21. package/dist/cdn/components/hexagram.js +9 -9
  22. package/dist/cdn/components/hexagram.js.map +3 -3
  23. package/dist/cdn/components/horoscope-card.js +28 -21
  24. package/dist/cdn/components/horoscope-card.js.map +4 -4
  25. package/dist/cdn/components/kp-planets-table.js +4 -4
  26. package/dist/cdn/components/kp-planets-table.js.map +1 -1
  27. package/dist/cdn/components/location-search.js.map +2 -2
  28. package/dist/cdn/components/moon-phase.js +13 -13
  29. package/dist/cdn/components/moon-phase.js.map +3 -3
  30. package/dist/cdn/components/natal-chart.js +196 -22
  31. package/dist/cdn/components/natal-chart.js.map +4 -4
  32. package/dist/cdn/components/numerology-card.js +6 -6
  33. package/dist/cdn/components/numerology-card.js.map +4 -4
  34. package/dist/cdn/components/panchang-table.js +9 -9
  35. package/dist/cdn/components/panchang-table.js.map +1 -1
  36. package/dist/cdn/components/shadbala-table.js +312 -0
  37. package/dist/cdn/components/shadbala-table.js.map +7 -0
  38. package/dist/cdn/components/synastry-chart.js +21 -21
  39. package/dist/cdn/components/synastry-chart.js.map +4 -4
  40. package/dist/cdn/components/transits-table.js +391 -0
  41. package/dist/cdn/components/transits-table.js.map +7 -0
  42. package/dist/cdn/components/vedic-kundli.js +51 -29
  43. package/dist/cdn/components/vedic-kundli.js.map +4 -4
  44. package/dist/cdn/components/yoga-list.js +334 -0
  45. package/dist/cdn/components/yoga-list.js.map +7 -0
  46. package/dist/cdn/roxy-ui.js +1872 -522
  47. package/dist/cdn/roxy-ui.js.map +4 -4
  48. package/dist/components/ashtakavarga-grid.d.ts +26 -0
  49. package/dist/components/ashtakavarga-grid.d.ts.map +1 -0
  50. package/dist/components/ashtakavarga-grid.js +457 -0
  51. package/dist/components/ashtakavarga-grid.js.map +7 -0
  52. package/dist/components/choghadiya-grid.d.ts +19 -0
  53. package/dist/components/choghadiya-grid.d.ts.map +1 -0
  54. package/dist/components/choghadiya-grid.js +304 -0
  55. package/dist/components/choghadiya-grid.js.map +7 -0
  56. package/dist/components/compatibility-card.js.map +1 -1
  57. package/dist/components/dasha-timeline.js.map +1 -1
  58. package/dist/components/data.d.ts +5 -7
  59. package/dist/components/data.d.ts.map +1 -1
  60. package/dist/components/data.js +7 -5
  61. package/dist/components/data.js.map +3 -3
  62. package/dist/components/divisional-chart.d.ts +20 -0
  63. package/dist/components/divisional-chart.d.ts.map +1 -0
  64. package/dist/components/divisional-chart.js +471 -0
  65. package/dist/components/divisional-chart.js.map +7 -0
  66. package/dist/components/dosha-card.d.ts.map +1 -1
  67. package/dist/components/dosha-card.js +33 -30
  68. package/dist/components/dosha-card.js.map +2 -2
  69. package/dist/components/endpoint-form.d.ts.map +1 -1
  70. package/dist/components/endpoint-form.js +5 -3
  71. package/dist/components/endpoint-form.js.map +3 -3
  72. package/dist/components/guna-milan.d.ts.map +1 -1
  73. package/dist/components/guna-milan.js +61 -12
  74. package/dist/components/guna-milan.js.map +3 -3
  75. package/dist/components/hexagram.js +17 -0
  76. package/dist/components/hexagram.js.map +2 -2
  77. package/dist/components/horoscope-card.d.ts.map +1 -1
  78. package/dist/components/horoscope-card.js +30 -3
  79. package/dist/components/horoscope-card.js.map +3 -3
  80. package/dist/components/kp-planets-table.js.map +1 -1
  81. package/dist/components/location-search.d.ts +2 -3
  82. package/dist/components/location-search.d.ts.map +1 -1
  83. package/dist/components/location-search.js.map +2 -2
  84. package/dist/components/moon-phase.js +17 -0
  85. package/dist/components/moon-phase.js.map +2 -2
  86. package/dist/components/natal-chart.d.ts +2 -0
  87. package/dist/components/natal-chart.d.ts.map +1 -1
  88. package/dist/components/natal-chart.js +243 -36
  89. package/dist/components/natal-chart.js.map +3 -3
  90. package/dist/components/numerology-card.d.ts.map +1 -1
  91. package/dist/components/numerology-card.js +5 -3
  92. package/dist/components/numerology-card.js.map +3 -3
  93. package/dist/components/panchang-table.js.map +1 -1
  94. package/dist/components/shadbala-table.d.ts +18 -0
  95. package/dist/components/shadbala-table.d.ts.map +1 -0
  96. package/dist/components/shadbala-table.js +400 -0
  97. package/dist/components/shadbala-table.js.map +7 -0
  98. package/dist/components/synastry-chart.d.ts.map +1 -1
  99. package/dist/components/synastry-chart.js +34 -29
  100. package/dist/components/synastry-chart.js.map +3 -3
  101. package/dist/components/transits-table.d.ts +21 -0
  102. package/dist/components/transits-table.d.ts.map +1 -0
  103. package/dist/components/transits-table.js +515 -0
  104. package/dist/components/transits-table.js.map +7 -0
  105. package/dist/components/vedic-kundli.d.ts +3 -6
  106. package/dist/components/vedic-kundli.d.ts.map +1 -1
  107. package/dist/components/vedic-kundli.js +132 -80
  108. package/dist/components/vedic-kundli.js.map +3 -3
  109. package/dist/components/yoga-list.d.ts +29 -0
  110. package/dist/components/yoga-list.d.ts.map +1 -0
  111. package/dist/components/yoga-list.js +389 -0
  112. package/dist/components/yoga-list.js.map +7 -0
  113. package/dist/index.cjs +2693 -971
  114. package/dist/index.cjs.map +4 -4
  115. package/dist/index.d.ts +7 -2
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +2712 -990
  118. package/dist/index.js.map +4 -4
  119. package/dist/manifest.d.ts +4 -10
  120. package/dist/manifest.d.ts.map +1 -1
  121. package/dist/manifest.json +7 -2
  122. package/dist/styles/tokens.css +26 -0
  123. package/dist/tokens/index.d.ts +6 -0
  124. package/dist/tokens/index.d.ts.map +1 -1
  125. package/dist/types/types.gen.d.ts +2 -2
  126. package/dist/utils/format.d.ts +15 -1
  127. package/dist/utils/format.d.ts.map +1 -1
  128. package/dist/utils/kundli-render.d.ts +63 -0
  129. package/dist/utils/kundli-render.d.ts.map +1 -0
  130. package/dist/utils/string.d.ts +14 -0
  131. package/dist/utils/string.d.ts.map +1 -0
  132. package/dist/version.d.ts +1 -1
  133. package/package.json +1 -1
  134. package/src/components/ashtakavarga-grid.ts +354 -0
  135. package/src/components/choghadiya-grid.ts +185 -0
  136. package/src/components/data.ts +8 -15
  137. package/src/components/divisional-chart.ts +214 -0
  138. package/src/components/dosha-card.ts +53 -36
  139. package/src/components/endpoint-form.ts +1 -7
  140. package/src/components/guna-milan.ts +74 -16
  141. package/src/components/horoscope-card.ts +8 -4
  142. package/src/components/location-search.ts +2 -3
  143. package/src/components/natal-chart.ts +251 -42
  144. package/src/components/numerology-card.ts +1 -7
  145. package/src/components/shadbala-table.ts +286 -0
  146. package/src/components/synastry-chart.ts +13 -39
  147. package/src/components/transits-table.ts +350 -0
  148. package/src/components/vedic-kundli.ts +38 -143
  149. package/src/components/yoga-list.ts +328 -0
  150. package/src/index.ts +8 -6
  151. package/src/manifest.ts +74 -100
  152. package/src/styles/tokens.css +26 -0
  153. package/src/tokens/index.ts +9 -0
  154. package/src/types/types.gen.ts +2 -2
  155. package/src/utils/format.ts +21 -3
  156. package/src/utils/kundli-render.ts +197 -0
  157. package/src/utils/string.ts +23 -0
  158. package/src/version.ts +1 -1
  159. package/dist/utils/motion.d.ts +0 -13
  160. package/dist/utils/motion.d.ts.map +0 -1
  161. package/src/utils/motion.ts +0 -18
@@ -1,90 +1,30 @@
1
- import { css, html, LitElement, nothing, svg } from 'lit';
1
+ import { css, html, LitElement } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
- import { PLANET_ABBR, SIGN_ABBR } from '../tokens/index.js';
3
+ import { RASHI_KEYS } from '../tokens/index.js';
4
4
  import type { BirthChartResponse } from '../types/index.js';
5
5
  import { baseStyles } from '../utils/base-styles.js';
6
+ import type { HouseDef } from '../utils/kundli-render.js';
7
+ import {
8
+ RASHI_TO_SIGN,
9
+ renderNorthFrame,
10
+ renderNorthHouseGroup,
11
+ renderSouthFrame,
12
+ renderSouthHouseGroup,
13
+ } from '../utils/kundli-render.js';
6
14
 
7
15
  type RashiBucket = BirthChartResponse['aries'];
8
16
 
9
- // TODO(spec): BirthChartResponse types only `aries` and `meta`, but the API
10
- // returns all 12 rashi keys (taurus, gemini, ..., pisces) with the same shape
11
- // as `aries`. Update the OpenAPI schema so the renderer can index by rashi
12
- // without casts.
17
+ // The /vedic-astrology/birth-chart response carries all 12 rashi keys
18
+ // (aries, taurus, ..., pisces), each shaped like the spec-typed `aries`
19
+ // bucket. This local alias indexes by rashi name without per-call casts.
13
20
  type BirthChartByRashi = BirthChartResponse & Record<string, RashiBucket>;
14
21
 
15
- interface KundliHouse {
16
- house: number;
17
- sign: string;
18
- planets: string[];
19
- }
20
-
21
- const SOUTH_HOUSE_CENTERS: Record<number, { x: number; y: number }> = {
22
- 1: { x: 150, y: 58 },
23
- 2: { x: 205, y: 52 },
24
- 3: { x: 253, y: 112 },
25
- 4: { x: 243, y: 150 },
26
- 5: { x: 253, y: 188 },
27
- 6: { x: 205, y: 248 },
28
- 7: { x: 150, y: 242 },
29
- 8: { x: 95, y: 248 },
30
- 9: { x: 47, y: 188 },
31
- 10: { x: 57, y: 150 },
32
- 11: { x: 47, y: 112 },
33
- 12: { x: 95, y: 52 },
34
- };
35
-
36
- const SOUTH_SIGN_POSITIONS: Record<number, { x: number; y: number }> = {
37
- 1: { x: 150, y: 35 },
38
- 2: { x: 222, y: 40 },
39
- 3: { x: 265, y: 100 },
40
- 4: { x: 265, y: 150 },
41
- 5: { x: 265, y: 200 },
42
- 6: { x: 222, y: 260 },
43
- 7: { x: 150, y: 265 },
44
- 8: { x: 78, y: 260 },
45
- 9: { x: 35, y: 200 },
46
- 10: { x: 35, y: 150 },
47
- 11: { x: 35, y: 100 },
48
- 12: { x: 78, y: 40 },
49
- };
50
-
51
- const RASHI_KEYS = [
52
- 'aries',
53
- 'taurus',
54
- 'gemini',
55
- 'cancer',
56
- 'leo',
57
- 'virgo',
58
- 'libra',
59
- 'scorpio',
60
- 'sagittarius',
61
- 'capricorn',
62
- 'aquarius',
63
- 'pisces',
64
- ] as const;
65
-
66
- const RASHI_TO_SIGN: Record<string, string> = {
67
- aries: 'Aries',
68
- taurus: 'Taurus',
69
- gemini: 'Gemini',
70
- cancer: 'Cancer',
71
- leo: 'Leo',
72
- virgo: 'Virgo',
73
- libra: 'Libra',
74
- scorpio: 'Scorpio',
75
- sagittarius: 'Sagittarius',
76
- capricorn: 'Capricorn',
77
- aquarius: 'Aquarius',
78
- pisces: 'Pisces',
79
- };
80
-
81
22
  /**
82
23
  * Vedic kundli (D1 Rashi chart). South Indian style by default. Pass `data`
83
- * from /vedic-astrology/birth-chart. North Indian style via style="north".
24
+ * from /vedic-astrology/birth-chart. North Indian style via chartStyle="north".
84
25
  *
85
- * Lifted from jyotish-vedic-astrology-app/src/components/birth-chart.tsx,
86
- * keeping HOUSE_CENTERS + SIGN_POSITIONS + abbreviations, dropping the React
87
- * DOM color-probing hook in favor of CSS custom properties on :host.
26
+ * Theming flows through CSS custom properties on :host, so the chart adopts
27
+ * the host page palette without runtime color probing.
88
28
  */
89
29
  @customElement('roxy-vedic-kundli')
90
30
  export class RoxyVedicKundli extends LitElement {
@@ -122,6 +62,12 @@ export class RoxyVedicKundli extends LitElement {
122
62
  font-weight: 600;
123
63
  font-family: var(--roxy-font-sans);
124
64
  }
65
+ .house-num {
66
+ fill: var(--roxy-muted, #71717a);
67
+ font-size: 9px;
68
+ font-weight: 400;
69
+ font-family: var(--roxy-font-sans);
70
+ }
125
71
  .lagna-marker {
126
72
  fill: var(--roxy-accent-fg, #b45309);
127
73
  font-size: 8px;
@@ -143,18 +89,23 @@ export class RoxyVedicKundli extends LitElement {
143
89
  @property({ type: String, reflect: true, attribute: 'chart-style' })
144
90
  chartStyle: 'south' | 'north' = 'south';
145
91
 
146
- private buildHouses(): KundliHouse[] {
92
+ private buildHouses(): HouseDef[] {
147
93
  if (!this.data) return [];
148
94
  const data = this.data as BirthChartByRashi;
149
- const houses: KundliHouse[] = [];
95
+ const lagnaSign = this.data?.meta?.Lagna?.rashi ?? '';
96
+ const houses: HouseDef[] = [];
150
97
  for (let i = 0; i < 12; i++) {
151
98
  const key = RASHI_KEYS[i];
152
99
  const bucket = data[key];
153
100
  const planets = (bucket?.signs ?? []).map((p) => p.graha).filter(Boolean);
101
+ const sign = RASHI_TO_SIGN[key] ?? '';
154
102
  houses.push({
155
- house: i + 1,
156
- sign: RASHI_TO_SIGN[key] ?? '',
103
+ number: i + 1,
104
+ sign,
157
105
  planets,
106
+ isLagna: lagnaSign
107
+ ? lagnaSign.toLowerCase() === sign.toLowerCase()
108
+ : false,
158
109
  });
159
110
  }
160
111
  return houses;
@@ -164,6 +115,7 @@ export class RoxyVedicKundli extends LitElement {
164
115
  if (!this.data)
165
116
  return html`<div class="roxy-empty" role="status">No kundli data</div>`;
166
117
  const houses = this.buildHouses();
118
+ const isNorth = this.chartStyle === 'north';
167
119
 
168
120
  return html`<div class="wrap">
169
121
  <h2 class="title">Vedic kundli</h2>
@@ -173,74 +125,17 @@ export class RoxyVedicKundli extends LitElement {
173
125
  aria-label="Vedic birth chart with twelve sign houses"
174
126
  >
175
127
  <title>Vedic kundli</title>
176
- <polygon class="line" points="150,10 290,150 150,290 10,150" stroke-width="1.5" />
177
- <polygon
178
- class="line"
179
- points="220,80 220,220 80,220 80,80"
180
- stroke-width="1"
181
- fill="none"
182
- />
183
- <line class="line" x1="150" y1="10" x2="80" y2="80" stroke-width="1" />
184
- <line class="line" x1="150" y1="10" x2="220" y2="80" stroke-width="1" />
185
- <line class="line" x1="290" y1="150" x2="220" y2="80" stroke-width="1" />
186
- <line class="line" x1="290" y1="150" x2="220" y2="220" stroke-width="1" />
187
- <line class="line" x1="150" y1="290" x2="220" y2="220" stroke-width="1" />
188
- <line class="line" x1="150" y1="290" x2="80" y2="220" stroke-width="1" />
189
- <line class="line" x1="10" y1="150" x2="80" y2="220" stroke-width="1" />
190
- <line class="line" x1="10" y1="150" x2="80" y2="80" stroke-width="1" />
191
- ${houses.map((h) => this.renderHouseGroup(h))}
192
- </svg>
193
- </div>`;
194
- }
195
-
196
- private isLagna(h: KundliHouse): boolean {
197
- const ascSign = this.data?.meta?.Lagna?.rashi;
198
- if (!ascSign) return false;
199
- return ascSign.toLowerCase() === h.sign.toLowerCase();
200
- }
201
-
202
- private renderHouseGroup(h: KundliHouse) {
203
- const center = SOUTH_HOUSE_CENTERS[h.house];
204
- const signPos = SOUTH_SIGN_POSITIONS[h.house];
205
- if (!center || !signPos) return nothing;
206
- const signAbbr = SIGN_ABBR[h.sign] ?? '';
207
- const planets = h.planets ?? [];
208
- const isLagna = this.isLagna(h);
209
- return svg`
210
- <g>
211
- ${
212
- isLagna
213
- ? svg`<rect class="lagna-bg" x=${center.x - 30} y=${center.y - 28} width="60" height="56" rx="6" />`
214
- : nothing
215
- }
128
+ ${isNorth ? renderNorthFrame() : renderSouthFrame()}
216
129
  ${
217
- signAbbr
218
- ? svg`<text class="sign-text" x=${signPos.x} y=${signPos.y} text-anchor="middle" dominant-baseline="central">${signAbbr}</text>`
219
- : nothing
130
+ isNorth
131
+ ? houses.map((h) => renderNorthHouseGroup(h))
132
+ : houses.map((h) => renderSouthHouseGroup(h))
220
133
  }
221
- ${
222
- isLagna
223
- ? svg`<text class="lagna-marker" x=${center.x} y=${center.y - 18} text-anchor="middle" dominant-baseline="central">LAGNA</text>`
224
- : nothing
225
- }
226
- ${planets.map((planet, j) => {
227
- const abbr = PLANET_ABBR[capitalize(planet)] ?? planet.slice(0, 2);
228
- const lineHeight = 13;
229
- const baseY = isLagna ? center.y + 8 : center.y;
230
- const startY = baseY - ((planets.length - 1) * lineHeight) / 2;
231
- const yPos = startY + j * lineHeight;
232
- return svg`<text class="planet-text" x=${center.x} y=${yPos} text-anchor="middle" dominant-baseline="central">${abbr}</text>`;
233
- })}
234
- </g>
235
- `;
134
+ </svg>
135
+ </div>`;
236
136
  }
237
137
  }
238
138
 
239
- function capitalize(s: string): string {
240
- if (!s) return '';
241
- return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
242
- }
243
-
244
139
  declare global {
245
140
  interface HTMLElementTagNameMap {
246
141
  'roxy-vedic-kundli': RoxyVedicKundli;
@@ -0,0 +1,328 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import type { GetYogaResponse, ListYogasResponse } from '../types/index.js';
4
+ import { baseStyles } from '../utils/base-styles.js';
5
+ import { debounce } from '../utils/debounce.js';
6
+
7
+ type YogaListData =
8
+ | ListYogasResponse
9
+ | GetYogaResponse
10
+ | { yogas: Array<GetYogaResponse> };
11
+
12
+ /**
13
+ * Yoga catalog and detail renderer. Accepts three data modes:
14
+ * - Catalog: ListYogasResponse (yogas array of {id, name} + total)
15
+ * - Detail: GetYogaResponse (single yoga with description, result, quality)
16
+ * - Detail array: { yogas: Array<GetYogaResponse> } for pre-filtered sets
17
+ *
18
+ * Catalog and detail-array modes include a live search filter.
19
+ */
20
+ @customElement('roxy-yoga-list')
21
+ export class RoxyYogaList extends LitElement {
22
+ static styles = [
23
+ baseStyles,
24
+ css`
25
+ .wrap {
26
+ display: grid;
27
+ gap: var(--roxy-space-md, 1rem);
28
+ }
29
+ .head {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: baseline;
33
+ flex-wrap: wrap;
34
+ gap: var(--roxy-space-sm, 0.5rem);
35
+ }
36
+ .title {
37
+ font-size: var(--roxy-text-lg, 1.125rem);
38
+ font-weight: var(--roxy-weight-bold, 600);
39
+ margin: 0;
40
+ }
41
+ .count {
42
+ font-size: var(--roxy-text-sm, 0.875rem);
43
+ color: var(--roxy-muted, #71717a);
44
+ }
45
+ .search-wrap {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: var(--roxy-space-sm, 0.5rem);
49
+ }
50
+ .search {
51
+ width: 100%;
52
+ max-width: 280px;
53
+ padding: 0.35em 0.75em;
54
+ font-size: var(--roxy-text-sm, 0.875rem);
55
+ font-family: var(--roxy-font-sans);
56
+ border: 1px solid var(--roxy-border, #e4e4e7);
57
+ border-radius: var(--roxy-radius-md, 8px);
58
+ background: var(--roxy-bg, #fff);
59
+ color: var(--roxy-fg, #0a0a0a);
60
+ outline: none;
61
+ }
62
+ .search::placeholder {
63
+ color: var(--roxy-fg, #0a0a0a);
64
+ opacity: 0.65;
65
+ }
66
+ .search:focus {
67
+ border-color: var(--roxy-accent, #f59e0b);
68
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--roxy-accent, #f59e0b) 30%, transparent);
69
+ }
70
+ .grid {
71
+ display: grid;
72
+ gap: var(--roxy-space-sm, 0.5rem);
73
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
74
+ }
75
+ .yoga-chip {
76
+ padding: 0.4em 0.8em;
77
+ border: 1px solid var(--roxy-border, #e4e4e7);
78
+ border-radius: var(--roxy-radius-md, 8px);
79
+ font-size: var(--roxy-text-sm, 0.875rem);
80
+ background: var(--roxy-bg, #fff);
81
+ color: var(--roxy-fg, #0a0a0a);
82
+ word-break: break-word;
83
+ }
84
+ .yoga-chip .yoga-id {
85
+ display: block;
86
+ font-size: 0.7em;
87
+ color: var(--roxy-fg, #0a0a0a);
88
+ opacity: 0.75;
89
+ margin-top: 0.15em;
90
+ }
91
+ .detail-card {
92
+ border: 1px solid var(--roxy-border, #e4e4e7);
93
+ border-radius: var(--roxy-radius-md, 8px);
94
+ padding: var(--roxy-space-md, 1rem);
95
+ background: var(--roxy-bg, #fff);
96
+ display: grid;
97
+ gap: var(--roxy-space-sm, 0.5rem);
98
+ }
99
+ .detail-name {
100
+ font-size: var(--roxy-text-lg, 1.125rem);
101
+ font-weight: var(--roxy-weight-bold, 600);
102
+ margin: 0;
103
+ display: flex;
104
+ align-items: center;
105
+ gap: var(--roxy-space-sm, 0.5rem);
106
+ flex-wrap: wrap;
107
+ }
108
+ .quality-chip {
109
+ display: inline-block;
110
+ font-size: var(--roxy-text-xs, 0.75rem);
111
+ font-weight: 600;
112
+ padding: 0.15em 0.6em;
113
+ border-radius: 999px;
114
+ }
115
+ .quality-Positive {
116
+ background: color-mix(in srgb, var(--roxy-success, #22c55e) 18%, transparent);
117
+ color: var(--roxy-success-fg, #15803d);
118
+ border: 1px solid color-mix(in srgb, var(--roxy-success, #22c55e) 40%, transparent);
119
+ }
120
+ .quality-Negative {
121
+ background: color-mix(in srgb, var(--roxy-danger, #ef4444) 18%, transparent);
122
+ color: var(--roxy-danger-fg, #b91c1c);
123
+ border: 1px solid color-mix(in srgb, var(--roxy-danger, #ef4444) 40%, transparent);
124
+ }
125
+ .quality-Both {
126
+ background: color-mix(in srgb, var(--roxy-warning, #f59e0b) 18%, transparent);
127
+ color: var(--roxy-warning-fg, #b45309);
128
+ border: 1px solid color-mix(in srgb, var(--roxy-warning, #f59e0b) 40%, transparent);
129
+ }
130
+ .description {
131
+ font-size: var(--roxy-text-sm, 0.875rem);
132
+ color: var(--roxy-muted, #71717a);
133
+ margin: 0;
134
+ line-height: var(--roxy-leading-normal, 1.5);
135
+ }
136
+ details {
137
+ font-size: var(--roxy-text-sm, 0.875rem);
138
+ }
139
+ details summary {
140
+ cursor: pointer;
141
+ color: var(--roxy-accent-fg, #b45309);
142
+ font-weight: 500;
143
+ padding: 0.25em 0;
144
+ list-style: none;
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 0.4em;
148
+ }
149
+ details summary::before {
150
+ content: '+';
151
+ font-size: 1.1em;
152
+ line-height: 1;
153
+ }
154
+ details[open] summary::before {
155
+ content: '-';
156
+ }
157
+ details .result-body {
158
+ padding-top: var(--roxy-space-xs, 0.25rem);
159
+ color: var(--roxy-fg, #0a0a0a);
160
+ line-height: var(--roxy-leading-normal, 1.5);
161
+ }
162
+ .no-results {
163
+ color: var(--roxy-muted, #71717a);
164
+ font-size: var(--roxy-text-sm, 0.875rem);
165
+ padding: var(--roxy-space-md, 1rem) 0;
166
+ text-align: center;
167
+ }
168
+ .detail-grid {
169
+ display: grid;
170
+ gap: var(--roxy-space-sm, 0.5rem);
171
+ }
172
+ `,
173
+ ];
174
+
175
+ @property({ attribute: false })
176
+ data: YogaListData | null = null;
177
+
178
+ @state()
179
+ private filter = '';
180
+
181
+ private readonly handleInput = debounce((e: Event) => {
182
+ this.filter = (e.target as HTMLInputElement).value;
183
+ }, 200);
184
+
185
+ private renderQualityChip(quality: string) {
186
+ const cls = `quality-chip quality-${quality}`;
187
+ return html`<span class=${cls}>${quality}</span>`;
188
+ }
189
+
190
+ private renderDetailCard(yoga: GetYogaResponse) {
191
+ return html`<div class="detail-card">
192
+ <p class="detail-name">
193
+ ${yoga.name}
194
+ ${yoga.quality ? this.renderQualityChip(yoga.quality) : nothing}
195
+ </p>
196
+ ${
197
+ yoga.description
198
+ ? html`<p class="description">${yoga.description}</p>`
199
+ : nothing
200
+ }
201
+ ${
202
+ yoga.result
203
+ ? html`<details>
204
+ <summary>Effects</summary>
205
+ <div class="result-body">${yoga.result}</div>
206
+ </details>`
207
+ : nothing
208
+ }
209
+ </div>`;
210
+ }
211
+
212
+ render() {
213
+ if (!this.data)
214
+ return html`<div class="roxy-empty" role="status">No yoga data</div>`;
215
+
216
+ const d = this.data;
217
+ const lc = this.filter.toLowerCase();
218
+
219
+ // Detail mode: single GetYogaResponse
220
+ if (
221
+ 'description' in d &&
222
+ typeof (d as GetYogaResponse).description === 'string'
223
+ ) {
224
+ const yoga = d as GetYogaResponse;
225
+ return html`<div class="wrap">${this.renderDetailCard(yoga)}</div>`;
226
+ }
227
+
228
+ // Detail-array mode: { yogas: Array<GetYogaResponse> } where items have description
229
+ if ('yogas' in d && Array.isArray((d as { yogas: unknown[] }).yogas)) {
230
+ const allYogas = (
231
+ d as { yogas: Array<GetYogaResponse | { id: string; name: string }> }
232
+ ).yogas;
233
+ const isDetailArray = allYogas.length > 0 && 'description' in allYogas[0];
234
+
235
+ if (isDetailArray) {
236
+ const detailYogas = allYogas as GetYogaResponse[];
237
+ const filtered = lc
238
+ ? detailYogas.filter((y) => y.name.toLowerCase().includes(lc))
239
+ : detailYogas;
240
+ const total = (d as ListYogasResponse).total;
241
+ return html`<div class="wrap">
242
+ <div class="head">
243
+ <h2 class="title">Yoga catalog</h2>
244
+ ${
245
+ total !== undefined
246
+ ? html`<span class="count">${total} total</span>`
247
+ : nothing
248
+ }
249
+ </div>
250
+ <div class="search-wrap">
251
+ <input
252
+ class="search"
253
+ type="search"
254
+ placeholder="Filter yogas..."
255
+ aria-label="Filter yoga list by name"
256
+ .value=${this.filter}
257
+ @input=${this.handleInput}
258
+ />
259
+ </div>
260
+ <div
261
+ class="detail-grid"
262
+ role="region"
263
+ aria-live="polite"
264
+ aria-label="Yoga results"
265
+ >
266
+ ${
267
+ filtered.length > 0
268
+ ? filtered.map((y) => this.renderDetailCard(y))
269
+ : html`<p class="no-results">No yogas match your search.</p>`
270
+ }
271
+ </div>
272
+ </div>`;
273
+ }
274
+
275
+ // Catalog mode: ListYogasResponse with {id, name} items
276
+ const catalogYogas = allYogas as Array<{ id: string; name: string }>;
277
+ const filtered = lc
278
+ ? catalogYogas.filter((y) => y.name.toLowerCase().includes(lc))
279
+ : catalogYogas;
280
+ const total = (d as ListYogasResponse).total;
281
+ return html`<div class="wrap">
282
+ <div class="head">
283
+ <h2 class="title">Yoga catalog</h2>
284
+ ${
285
+ total !== undefined
286
+ ? html`<span class="count">${total} total</span>`
287
+ : nothing
288
+ }
289
+ </div>
290
+ <div class="search-wrap">
291
+ <input
292
+ class="search"
293
+ type="search"
294
+ placeholder="Filter yogas..."
295
+ aria-label="Filter yoga list by name"
296
+ .value=${this.filter}
297
+ @input=${this.handleInput}
298
+ />
299
+ </div>
300
+ <div
301
+ class="grid"
302
+ role="region"
303
+ aria-live="polite"
304
+ aria-label="Yoga results"
305
+ >
306
+ ${
307
+ filtered.length > 0
308
+ ? filtered.map(
309
+ (y) => html`<div class="yoga-chip">
310
+ ${y.name}
311
+ <span class="yoga-id">${y.id}</span>
312
+ </div>`,
313
+ )
314
+ : html`<p class="no-results">No yogas match your search.</p>`
315
+ }
316
+ </div>
317
+ </div>`;
318
+ }
319
+
320
+ return html`<div class="roxy-empty" role="status">No yoga data</div>`;
321
+ }
322
+ }
323
+
324
+ declare global {
325
+ interface HTMLElementTagNameMap {
326
+ 'roxy-yoga-list': RoxyYogaList;
327
+ }
328
+ }
package/src/index.ts CHANGED
@@ -5,12 +5,15 @@
5
5
  * import '@roxyapi/ui/components/natal-chart';
6
6
  */
7
7
 
8
+ export { RoxyAshtakavargaGrid } from './components/ashtakavarga-grid.js';
8
9
  // Biorhythm
9
10
  export { RoxyBiorhythmChart } from './components/biorhythm-chart.js';
11
+ export { RoxyChoghadiyaGrid } from './components/choghadiya-grid.js';
10
12
  export { RoxyCompatibilityCard } from './components/compatibility-card.js';
11
13
  export { RoxyDashaTimeline } from './components/dasha-timeline.js';
12
14
  // Generic fallback first so it is always available for nested rendering
13
15
  export { RoxyData } from './components/data.js';
16
+ export { RoxyDivisionalChart } from './components/divisional-chart.js';
14
17
  export { RoxyDoshaCard } from './components/dosha-card.js';
15
18
  // Helpers
16
19
  export { RoxyEndpointForm } from './components/endpoint-form.js';
@@ -21,17 +24,20 @@ export { RoxyHoroscopeCard } from './components/horoscope-card.js';
21
24
  export { RoxyKpPlanetsTable } from './components/kp-planets-table.js';
22
25
  export { RoxyLocationSearch } from './components/location-search.js';
23
26
  export { RoxyMoonPhase } from './components/moon-phase.js';
24
- // Western
27
+ // Western astrology
25
28
  export { RoxyNatalChart } from './components/natal-chart.js';
26
29
  // Numerology
27
30
  export { RoxyNumerologyCard } from './components/numerology-card.js';
28
31
  export { RoxyPanchangTable } from './components/panchang-table.js';
32
+ export { RoxyShadbalaTable } from './components/shadbala-table.js';
29
33
  export { RoxySynastryChart } from './components/synastry-chart.js';
30
34
  // Tarot
31
35
  export { RoxyTarotCard } from './components/tarot-card.js';
32
36
  export { RoxyTarotSpread } from './components/tarot-spread.js';
33
- // Vedic
37
+ export { RoxyTransitsTable } from './components/transits-table.js';
38
+ // Vedic astrology
34
39
  export { RoxyVedicKundli } from './components/vedic-kundli.js';
40
+ export { RoxyYogaList } from './components/yoga-list.js';
35
41
 
36
42
  import { ROXY_COMPONENTS, type RoxyComponentSlug } from './manifest.js';
37
43
 
@@ -39,13 +45,9 @@ export {
39
45
  ROXY_COMPONENTS,
40
46
  type RoxyComponent,
41
47
  type RoxyComponentSlug,
42
- type RoxyComponentTag,
43
- type RoxyDomain,
44
48
  } from './manifest.js';
45
49
  export { ROXY_UI_VERSION } from './version.js';
46
50
 
47
51
  /** Slugs in declaration order. Kept for the auto-mount widgets script and downstream codegen. */
48
52
  export const ROXY_UI_COMPONENTS: readonly RoxyComponentSlug[] =
49
53
  ROXY_COMPONENTS.map((c) => c.slug) as RoxyComponentSlug[];
50
-
51
- export type RoxyUIComponentName = RoxyComponentSlug;