@roxyapi/ui 0.1.2 → 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/README.md +321 -14
- 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 +7 -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
package/src/components/data.ts
CHANGED
|
@@ -25,6 +25,14 @@ const TITLE_KEYS = ['title', 'name', 'label', 'heading', 'overview', 'summary'];
|
|
|
25
25
|
const IMAGE_KEYS = ['imageUrl', 'image', 'icon', 'symbol'];
|
|
26
26
|
const SKIP_KEYS = ['imageUrl', 'image']; // rendered separately, not in body rows
|
|
27
27
|
|
|
28
|
+
// Hard cap on recursion. Real RoxyAPI responses nest at most 5-6 deep; anything
|
|
29
|
+
// deeper is either a circular reference (which would otherwise infinite-loop)
|
|
30
|
+
// or a payload too rich for the generic fallback to render usefully. The
|
|
31
|
+
// recursion is otherwise safe: <roxy-data> is registered globally by its
|
|
32
|
+
// `@customElement` decorator on import, so the nested template resolves to
|
|
33
|
+
// this same class without a separate import.
|
|
34
|
+
const MAX_DEPTH = 6;
|
|
35
|
+
|
|
28
36
|
@customElement('roxy-data')
|
|
29
37
|
export class RoxyData extends LitElement {
|
|
30
38
|
static styles = [
|
|
@@ -129,10 +137,21 @@ export class RoxyData extends LitElement {
|
|
|
129
137
|
@property({ attribute: false })
|
|
130
138
|
data: Json = null;
|
|
131
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Internal recursion depth. Nested <roxy-data> instances inherit this from
|
|
142
|
+
* the parent and increment to guard against circular references in the
|
|
143
|
+
* input. Not part of the public API; do not set from consumer code.
|
|
144
|
+
*/
|
|
145
|
+
@property({ attribute: false })
|
|
146
|
+
depth = 0;
|
|
147
|
+
|
|
132
148
|
render() {
|
|
133
149
|
if (this.data == null) {
|
|
134
150
|
return html`<div class="roxy-empty" role="status">No data</div>`;
|
|
135
151
|
}
|
|
152
|
+
if (this.depth >= MAX_DEPTH) {
|
|
153
|
+
return html`<div class="roxy-empty" role="status">…</div>`;
|
|
154
|
+
}
|
|
136
155
|
return html`<div
|
|
137
156
|
class="roxy-card"
|
|
138
157
|
aria-label="Generic data display"
|
|
@@ -252,7 +271,7 @@ export class RoxyData extends LitElement {
|
|
|
252
271
|
</ul>`;
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
|
-
return html`<roxy-data .data=${value}></roxy-data>`;
|
|
274
|
+
return html`<roxy-data .data=${value} .depth=${this.depth + 1}></roxy-data>`;
|
|
256
275
|
}
|
|
257
276
|
|
|
258
277
|
private formatPrimitive(value: Json | undefined): string {
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { css, html, LitElement, nothing } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import type {
|
|
4
|
+
KalsarpaResponse,
|
|
5
|
+
ManglikResponse,
|
|
6
|
+
SadhesatiResponse,
|
|
7
|
+
} from '../types/index.js';
|
|
3
8
|
import { baseStyles } from '../utils/base-styles.js';
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
present?: boolean;
|
|
7
|
-
severity?: 'Mild' | 'Moderate' | 'Severe' | string;
|
|
8
|
-
type?: string;
|
|
9
|
-
description?: string;
|
|
10
|
-
remedies?: string[];
|
|
11
|
-
exceptions?: string[];
|
|
12
|
-
effects?:
|
|
13
|
-
| string
|
|
14
|
-
| {
|
|
15
|
-
marriage?: string;
|
|
16
|
-
personality?: string;
|
|
17
|
-
timing?: string;
|
|
18
|
-
relationships?: string;
|
|
19
|
-
general?: string;
|
|
20
|
-
phases?: Record<string, string>;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
10
|
+
type DoshaData = ManglikResponse | KalsarpaResponse | SadhesatiResponse;
|
|
23
11
|
|
|
24
12
|
const DOSHA_LABELS: Record<string, string> = {
|
|
25
13
|
manglik: 'Mangal Dosha',
|
|
@@ -71,11 +59,11 @@ export class RoxyDoshaCard extends LitElement {
|
|
|
71
59
|
}
|
|
72
60
|
.badge.absent {
|
|
73
61
|
background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
|
|
74
|
-
color: var(--roxy-success, #
|
|
62
|
+
color: var(--roxy-success-fg, #166534);
|
|
75
63
|
}
|
|
76
64
|
.badge.present {
|
|
77
65
|
background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
|
|
78
|
-
color: var(--roxy-danger, #
|
|
66
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
79
67
|
}
|
|
80
68
|
.severity {
|
|
81
69
|
display: flex;
|
|
@@ -166,7 +154,7 @@ export class RoxyDoshaCard extends LitElement {
|
|
|
166
154
|
</div>
|
|
167
155
|
</header>
|
|
168
156
|
${d.description ? html`<p class="description">${d.description}</p>` : nothing}
|
|
169
|
-
${this.renderEffects(d
|
|
157
|
+
${this.renderEffects(d)}
|
|
170
158
|
${
|
|
171
159
|
d.remedies && d.remedies.length > 0
|
|
172
160
|
? html`<div>
|
|
@@ -178,22 +166,21 @@ export class RoxyDoshaCard extends LitElement {
|
|
|
178
166
|
: nothing
|
|
179
167
|
}
|
|
180
168
|
${
|
|
181
|
-
d.exceptions && d.exceptions.length > 0
|
|
169
|
+
'exceptions' in d && d.exceptions && d.exceptions.length > 0
|
|
182
170
|
? html`<div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
<h3>Exceptions</h3>
|
|
172
|
+
<ul>
|
|
173
|
+
${d.exceptions.map((r) => html`<li>${r}</li>`)}
|
|
174
|
+
</ul>
|
|
175
|
+
</div>`
|
|
188
176
|
: nothing
|
|
189
177
|
}
|
|
190
178
|
</article>`;
|
|
191
179
|
}
|
|
192
180
|
|
|
193
|
-
private renderEffects(
|
|
194
|
-
if (!
|
|
195
|
-
|
|
196
|
-
const entries = Object.entries(e).filter(
|
|
181
|
+
private renderEffects(d: DoshaData) {
|
|
182
|
+
if (!d.effects) return nothing;
|
|
183
|
+
const entries = Object.entries(d.effects).filter(
|
|
197
184
|
([, v]) => typeof v === 'string' && v.length > 0,
|
|
198
185
|
);
|
|
199
186
|
if (entries.length === 0) return nothing;
|
|
@@ -31,6 +31,33 @@ interface FieldDef {
|
|
|
31
31
|
default?: unknown;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
interface OpenApiDoc {
|
|
35
|
+
paths?: Record<string, Record<string, unknown>>;
|
|
36
|
+
components?: { schemas?: Record<string, OpenApiSchema> };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const specCache = new Map<string, Promise<OpenApiDoc>>();
|
|
40
|
+
|
|
41
|
+
async function loadSpec(url: string): Promise<OpenApiDoc> {
|
|
42
|
+
let pending = specCache.get(url);
|
|
43
|
+
if (!pending) {
|
|
44
|
+
pending = fetch(url)
|
|
45
|
+
.then(async (res) => {
|
|
46
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
47
|
+
return (await res.json()) as OpenApiDoc;
|
|
48
|
+
})
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
// Evict the rejected promise BEFORE rethrowing so subsequent
|
|
51
|
+
// callers (the user clicking Retry, a remount) hit the network
|
|
52
|
+
// again instead of replaying the cached failure forever.
|
|
53
|
+
specCache.delete(url);
|
|
54
|
+
throw err;
|
|
55
|
+
});
|
|
56
|
+
specCache.set(url, pending);
|
|
57
|
+
}
|
|
58
|
+
return pending;
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
62
|
* Schema-driven form. Pass `endpoint` (e.g. "vedic-astrology/birth-chart").
|
|
36
63
|
* The form introspects the cached OpenAPI spec, slots a roxy-location-search
|
|
@@ -64,22 +91,27 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
64
91
|
.fields {
|
|
65
92
|
display: grid;
|
|
66
93
|
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
94
|
+
align-items: start;
|
|
67
95
|
gap: var(--roxy-space-md, 1rem);
|
|
68
96
|
}
|
|
69
97
|
.field {
|
|
70
|
-
display:
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
71
100
|
gap: var(--roxy-space-xs, 0.25rem);
|
|
101
|
+
min-width: 0;
|
|
72
102
|
}
|
|
73
103
|
label {
|
|
74
104
|
font-size: var(--roxy-text-sm, 0.875rem);
|
|
75
105
|
color: var(--roxy-secondary, #475569);
|
|
76
106
|
}
|
|
77
107
|
label .req {
|
|
78
|
-
color: var(--roxy-danger, #
|
|
108
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
79
109
|
margin-left: 4px;
|
|
80
110
|
}
|
|
81
111
|
input,
|
|
82
112
|
select {
|
|
113
|
+
width: 100%;
|
|
114
|
+
box-sizing: border-box;
|
|
83
115
|
padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
|
|
84
116
|
font-size: var(--roxy-text-base, 1rem);
|
|
85
117
|
font-family: inherit;
|
|
@@ -114,7 +146,7 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
114
146
|
button.submit {
|
|
115
147
|
justify-self: start;
|
|
116
148
|
background: var(--roxy-accent-fg, #b45309);
|
|
117
|
-
color: #fff;
|
|
149
|
+
color: var(--roxy-bg, #fff);
|
|
118
150
|
border: 0;
|
|
119
151
|
border-radius: var(--roxy-radius-md, 8px);
|
|
120
152
|
padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-lg, 1.5rem);
|
|
@@ -132,6 +164,17 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
132
164
|
outline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));
|
|
133
165
|
outline-offset: 2px;
|
|
134
166
|
}
|
|
167
|
+
.spec-error {
|
|
168
|
+
display: grid;
|
|
169
|
+
gap: var(--roxy-space-md, 1rem);
|
|
170
|
+
justify-items: start;
|
|
171
|
+
background: var(--roxy-bg, #fff);
|
|
172
|
+
border: 1px solid var(--roxy-danger, #dc2626);
|
|
173
|
+
border-radius: var(--roxy-radius-md, 8px);
|
|
174
|
+
padding: var(--roxy-space-lg, 1.5rem);
|
|
175
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
176
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
177
|
+
}
|
|
135
178
|
`,
|
|
136
179
|
];
|
|
137
180
|
|
|
@@ -159,19 +202,18 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
159
202
|
@state()
|
|
160
203
|
private loaded = false;
|
|
161
204
|
|
|
205
|
+
@state()
|
|
206
|
+
private specError: string | null = null;
|
|
207
|
+
|
|
162
208
|
connectedCallback(): void {
|
|
163
209
|
super.connectedCallback();
|
|
164
210
|
void this.loadSchema();
|
|
165
211
|
}
|
|
166
212
|
|
|
167
213
|
private async loadSchema() {
|
|
214
|
+
this.specError = null;
|
|
168
215
|
try {
|
|
169
|
-
const
|
|
170
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
171
|
-
const spec = (await res.json()) as {
|
|
172
|
-
paths?: Record<string, Record<string, unknown>>;
|
|
173
|
-
components?: { schemas?: Record<string, OpenApiSchema> };
|
|
174
|
-
};
|
|
216
|
+
const spec = await loadSpec(this.specUrl);
|
|
175
217
|
const path = `/${this.endpoint.replace(/^\//, '')}`;
|
|
176
218
|
const op = spec.paths?.[path]?.[this.method.toLowerCase()] as
|
|
177
219
|
| {
|
|
@@ -189,7 +231,11 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
189
231
|
}>;
|
|
190
232
|
}
|
|
191
233
|
| undefined;
|
|
192
|
-
if (!op)
|
|
234
|
+
if (!op) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Endpoint ${this.method} ${path} not found in OpenAPI spec`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
193
239
|
|
|
194
240
|
const schemas = spec.components?.schemas ?? {};
|
|
195
241
|
const fields: FieldDef[] = [];
|
|
@@ -244,11 +290,26 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
244
290
|
}
|
|
245
291
|
this.values = init;
|
|
246
292
|
this.loaded = true;
|
|
247
|
-
} catch (
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
295
|
+
this.specError = message;
|
|
248
296
|
this.loaded = true;
|
|
297
|
+
this.dispatchEvent(
|
|
298
|
+
new CustomEvent('roxy-spec-error', {
|
|
299
|
+
detail: { url: this.specUrl, message },
|
|
300
|
+
bubbles: true,
|
|
301
|
+
composed: true,
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
249
304
|
}
|
|
250
305
|
}
|
|
251
306
|
|
|
307
|
+
private retryLoadSchema = () => {
|
|
308
|
+
this.loaded = false;
|
|
309
|
+
this.specError = null;
|
|
310
|
+
void this.loadSchema();
|
|
311
|
+
};
|
|
312
|
+
|
|
252
313
|
private resolve(
|
|
253
314
|
schema: OpenApiSchema | OpenApiSchemaRef | undefined,
|
|
254
315
|
all: Record<string, OpenApiSchema>,
|
|
@@ -322,6 +383,13 @@ export class RoxyEndpointForm extends LitElement {
|
|
|
322
383
|
return html`<form><div class="roxy-skeleton" style="height: 8rem"></div></form>`;
|
|
323
384
|
}
|
|
324
385
|
|
|
386
|
+
if (this.specError) {
|
|
387
|
+
return html`<div class="spec-error" role="alert">
|
|
388
|
+
Schema load failed: ${this.specError}
|
|
389
|
+
<button type="button" class="submit" @click=${this.retryLoadSchema}>Retry</button>
|
|
390
|
+
</div>`;
|
|
391
|
+
}
|
|
392
|
+
|
|
325
393
|
const renderField = (f: FieldDef) => {
|
|
326
394
|
if (
|
|
327
395
|
this.hasLocation &&
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
import { css, html, LitElement, nothing } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import type { CompatibilityResponse } from '../types/index.js';
|
|
3
4
|
import { baseStyles } from '../utils/base-styles.js';
|
|
4
|
-
|
|
5
|
-
interface GunaCategory {
|
|
6
|
-
name?: string;
|
|
7
|
-
score?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
maxScore?: number;
|
|
10
|
-
description?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface GunaData {
|
|
14
|
-
total?: number;
|
|
15
|
-
totalScore?: number;
|
|
16
|
-
maxScore?: number;
|
|
17
|
-
percentage?: number;
|
|
18
|
-
isCompatible?: boolean;
|
|
19
|
-
recommendation?: string;
|
|
20
|
-
doshas?: string[];
|
|
21
|
-
doshaCancellations?: string[];
|
|
22
|
-
breakdown?: GunaCategory[];
|
|
23
|
-
}
|
|
5
|
+
import { formatNumber, formatPercent } from '../utils/format.js';
|
|
24
6
|
|
|
25
7
|
const STANDARD_CATEGORIES = [
|
|
26
8
|
'Varna',
|
|
@@ -127,38 +109,36 @@ export class RoxyGunaMilan extends LitElement {
|
|
|
127
109
|
}
|
|
128
110
|
.tags .dosha {
|
|
129
111
|
background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
|
|
130
|
-
color: var(--roxy-danger, #
|
|
112
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
131
113
|
}
|
|
132
114
|
.tags .cancel {
|
|
133
115
|
background: color-mix(in srgb, var(--roxy-success, #16a34a) 18%, transparent);
|
|
134
|
-
color: var(--roxy-success, #
|
|
116
|
+
color: var(--roxy-success-fg, #166534);
|
|
135
117
|
}
|
|
136
118
|
`,
|
|
137
119
|
];
|
|
138
120
|
|
|
139
121
|
@property({ attribute: false })
|
|
140
|
-
data:
|
|
122
|
+
data: CompatibilityResponse | null = null;
|
|
141
123
|
|
|
142
124
|
render() {
|
|
143
125
|
const d = this.data;
|
|
144
126
|
if (!d)
|
|
145
127
|
return html`<div class="roxy-empty" role="status">No Guna Milan data</div>`;
|
|
146
128
|
|
|
147
|
-
const total = d.total ?? d.totalScore ?? 0;
|
|
148
|
-
const max = d.maxScore ?? 36;
|
|
149
129
|
const breakdown = (d.breakdown ?? []).filter(
|
|
150
|
-
(b) => b
|
|
130
|
+
(b) => b?.category !== undefined,
|
|
151
131
|
);
|
|
152
132
|
|
|
153
133
|
return html`<article class="card" aria-label="Guna Milan score">
|
|
154
134
|
<div class="score-bar">
|
|
155
135
|
<div>
|
|
156
|
-
<span class="total">${total}</span>
|
|
157
|
-
<span class="over"> / ${
|
|
136
|
+
<span class="total">${formatNumber(d.total, 1)}</span>
|
|
137
|
+
<span class="over"> / ${d.maxScore}</span>
|
|
158
138
|
${
|
|
159
139
|
typeof d.percentage === 'number'
|
|
160
140
|
? html`<small style="margin-left: 0.5rem; color: var(--roxy-muted)">
|
|
161
|
-
${d.percentage}
|
|
141
|
+
${formatPercent(d.percentage, 1)}
|
|
162
142
|
</small>`
|
|
163
143
|
: nothing
|
|
164
144
|
}
|
|
@@ -183,16 +163,16 @@ export class RoxyGunaMilan extends LitElement {
|
|
|
183
163
|
<tbody>
|
|
184
164
|
${breakdown.map((b) => {
|
|
185
165
|
const score = b.score ?? 0;
|
|
186
|
-
const maxScore = b.
|
|
166
|
+
const maxScore = b.maxScore ?? defaultMax(b.category);
|
|
187
167
|
const pct = maxScore ? (score / maxScore) * 100 : 0;
|
|
188
168
|
return html`<tr>
|
|
189
|
-
<td>${b.
|
|
169
|
+
<td>${b.category}</td>
|
|
190
170
|
<td class="bar-cell">
|
|
191
171
|
<div class="mini-bar">
|
|
192
172
|
<span style="width: ${pct}%"></span>
|
|
193
173
|
</div>
|
|
194
174
|
</td>
|
|
195
|
-
<td class="score">${score} / ${maxScore}</td>
|
|
175
|
+
<td class="score">${formatNumber(score, 1)} / ${maxScore}</td>
|
|
196
176
|
</tr>`;
|
|
197
177
|
})}
|
|
198
178
|
</tbody>
|
|
@@ -203,7 +183,10 @@ export class RoxyGunaMilan extends LitElement {
|
|
|
203
183
|
(d.doshas?.length ?? 0) > 0 || (d.doshaCancellations?.length ?? 0) > 0
|
|
204
184
|
? html`<div class="tags">
|
|
205
185
|
${d.doshas?.map((x) => html`<span class="dosha">${x}</span>`)}
|
|
206
|
-
${d.doshaCancellations?.map(
|
|
186
|
+
${d.doshaCancellations?.map(
|
|
187
|
+
(x) =>
|
|
188
|
+
html`<span class="cancel" title=${x.reason}>${x.dosha} cancelled</span>`,
|
|
189
|
+
)}
|
|
207
190
|
</div>`
|
|
208
191
|
: nothing
|
|
209
192
|
}
|
|
@@ -235,7 +218,6 @@ function defaultMax(name?: string): number {
|
|
|
235
218
|
}
|
|
236
219
|
}
|
|
237
220
|
|
|
238
|
-
// Reference list (kept for documentation, used at codegen time)
|
|
239
221
|
export const GUNA_CATEGORIES = STANDARD_CATEGORIES;
|
|
240
222
|
|
|
241
223
|
declare global {
|
|
@@ -1,34 +1,22 @@
|
|
|
1
1
|
import { css, html, LitElement, nothing, svg } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
3
|
import { TRIGRAM_GLYPH } from '../tokens/index.js';
|
|
4
|
+
import type {
|
|
5
|
+
CastReadingResponse,
|
|
6
|
+
GetDailyHexagramResponse,
|
|
7
|
+
GetHexagramResponse,
|
|
8
|
+
GetRandomHexagramResponse,
|
|
9
|
+
Hexagram,
|
|
10
|
+
LookupHexagramResponse,
|
|
11
|
+
} from '../types/index.js';
|
|
4
12
|
import { baseStyles } from '../utils/base-styles.js';
|
|
5
13
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
upperTrigram?: string;
|
|
13
|
-
lowerTrigram?: string;
|
|
14
|
-
judgment?: string;
|
|
15
|
-
image?: string;
|
|
16
|
-
interpretation?: {
|
|
17
|
-
general?: string;
|
|
18
|
-
love?: string;
|
|
19
|
-
career?: string;
|
|
20
|
-
decision?: string;
|
|
21
|
-
advice?: string;
|
|
22
|
-
};
|
|
23
|
-
changingLines?: number[];
|
|
24
|
-
resultingHexagram?: HexagramData;
|
|
25
|
-
dailyMessage?: string;
|
|
26
|
-
hexagram?: HexagramData;
|
|
27
|
-
lines?: number[]; // 6, 7, 8, 9 cast values
|
|
28
|
-
changingLinePositions?: number[];
|
|
29
|
-
seed?: string;
|
|
30
|
-
date?: string;
|
|
31
|
-
}
|
|
14
|
+
type HexagramData =
|
|
15
|
+
| GetHexagramResponse
|
|
16
|
+
| GetRandomHexagramResponse
|
|
17
|
+
| LookupHexagramResponse
|
|
18
|
+
| GetDailyHexagramResponse
|
|
19
|
+
| CastReadingResponse;
|
|
32
20
|
|
|
33
21
|
/**
|
|
34
22
|
* I Ching hexagram card. Renders /iching/hexagrams/{number}, /iching/cast,
|
|
@@ -154,25 +142,48 @@ export class RoxyHexagram extends LitElement {
|
|
|
154
142
|
@property({ type: String, reflect: true })
|
|
155
143
|
mode: 'lookup' | 'cast' | 'daily' = 'lookup';
|
|
156
144
|
|
|
157
|
-
private
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
private resolveHexagram(): {
|
|
146
|
+
hex: Hexagram;
|
|
147
|
+
lines?: number[];
|
|
148
|
+
changingLinePositions?: number[];
|
|
149
|
+
dailyMessage?: string;
|
|
150
|
+
resultingHexagram?: Hexagram;
|
|
151
|
+
} | null {
|
|
152
|
+
const d = this.data;
|
|
153
|
+
if (!d) return null;
|
|
154
|
+
if ('hexagram' in d && d.hexagram) {
|
|
155
|
+
if ('lines' in d) {
|
|
156
|
+
const cast = d as CastReadingResponse;
|
|
157
|
+
return {
|
|
158
|
+
hex: cast.hexagram as Hexagram,
|
|
159
|
+
lines: cast.lines,
|
|
160
|
+
changingLinePositions: cast.changingLinePositions,
|
|
161
|
+
resultingHexagram: cast.resultingHexagram as Hexagram | undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const daily = d as GetDailyHexagramResponse;
|
|
160
165
|
return {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
changingLinePositions: this.data.changingLinePositions,
|
|
166
|
+
hex: daily.hexagram as Hexagram,
|
|
167
|
+
dailyMessage: daily.dailyMessage,
|
|
164
168
|
};
|
|
165
169
|
}
|
|
166
|
-
return
|
|
170
|
+
return { hex: d as Hexagram };
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
render() {
|
|
170
|
-
const
|
|
171
|
-
if (!
|
|
174
|
+
const resolved = this.resolveHexagram();
|
|
175
|
+
if (!resolved)
|
|
172
176
|
return html`<div class="roxy-empty" role="status">No hexagram data</div>`;
|
|
173
177
|
|
|
174
|
-
const
|
|
175
|
-
|
|
178
|
+
const {
|
|
179
|
+
hex: h,
|
|
180
|
+
lines: castLines,
|
|
181
|
+
changingLinePositions,
|
|
182
|
+
dailyMessage,
|
|
183
|
+
resultingHexagram,
|
|
184
|
+
} = resolved;
|
|
185
|
+
const lines = castLines ?? this.derivedLines(h);
|
|
186
|
+
const changing = new Set(changingLinePositions ?? []);
|
|
176
187
|
|
|
177
188
|
return html`<article class="card" aria-label="I Ching hexagram">
|
|
178
189
|
<div class="glyphs">
|
|
@@ -229,7 +240,7 @@ export class RoxyHexagram extends LitElement {
|
|
|
229
240
|
</div>
|
|
230
241
|
${h.judgment ? html`<p class="judgment">${h.judgment}</p>` : nothing}
|
|
231
242
|
${h.image ? html`<p class="image">${h.image}</p>` : nothing}
|
|
232
|
-
${
|
|
243
|
+
${dailyMessage ? html`<p class="message">${dailyMessage}</p>` : nothing}
|
|
233
244
|
${
|
|
234
245
|
h.interpretation?.general
|
|
235
246
|
? html`<p>${h.interpretation.general}</p>`
|
|
@@ -242,9 +253,9 @@ export class RoxyHexagram extends LitElement {
|
|
|
242
253
|
.sort((a, b) => a - b)
|
|
243
254
|
.join(', ')}.
|
|
244
255
|
${
|
|
245
|
-
|
|
246
|
-
? html` Becomes hexagram ${
|
|
247
|
-
${
|
|
256
|
+
resultingHexagram?.english
|
|
257
|
+
? html` Becomes hexagram ${resultingHexagram.number}
|
|
258
|
+
${resultingHexagram.english}.`
|
|
248
259
|
: nothing
|
|
249
260
|
}
|
|
250
261
|
</div>`
|
|
@@ -255,8 +266,7 @@ export class RoxyHexagram extends LitElement {
|
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
/** When the API only ships symbol+number with no line array, render six solid yang. */
|
|
258
|
-
private derivedLines(h:
|
|
259
|
-
if (!h.symbol) return Array.from({ length: 6 }, () => 7);
|
|
269
|
+
private derivedLines(h: Hexagram): number[] {
|
|
260
270
|
// Map each character of the unicode hexagram block (U+4DC0..) to broken/solid
|
|
261
271
|
const cp = h.symbol.codePointAt(0) ?? 0;
|
|
262
272
|
if (cp >= 0x4dc0 && cp <= 0x4dff) {
|