lichta 1.0.0-beta

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 ADDED
@@ -0,0 +1,245 @@
1
+ # 🌙 LichTa
2
+
3
+ **Thư viện chuyển đổi Dương lịch ↔ Âm lịch Việt Nam cho JavaScript/TypeScript.**
4
+
5
+ Hỗ trợ Svelte 5 components ăn liền, đồng thời export pure TypeScript core cho React, Vue, Angular, và vanilla JS.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/lichta)](https://www.npmjs.com/package/lichta)
8
+ [![license](https://img.shields.io/npm/l/lichta)](https://github.com/juststridev/lichta/blob/main/LICENSE)
9
+
10
+ ---
11
+
12
+ ## ✨ Tính năng
13
+
14
+ - 🔄 **Chuyển đổi Solar ↔ Lunar** — Thuật toán Hồ Ngọc Đức (phạm vi 1800–2199)
15
+ - 🐉 **Can Chi** — Tính Can Chi cho năm, tháng, ngày, giờ
16
+ - 🔮 **Ngũ Hành & Mệnh** — Tính mệnh theo năm sinh
17
+ - ⏰ **Giờ Hoàng Đạo** — 6 giờ tốt trong ngày
18
+ - 📅 **Calendar Component** — Lịch tháng Svelte 5 với ngày âm lịch
19
+ - 🎨 **Format** — Hiển thị ngày âm lịch truyền thống (Mùng Một tháng Giêng...)
20
+ - 🌐 **i18n** — Hỗ trợ Việt–Anh–Nhật–Hàn (vi, en, ja, ko)
21
+ - 📦 **Multi-framework** — Svelte 5, React, Vue, Angular, vanilla JS
22
+
23
+ ---
24
+
25
+ ## 📦 Cài đặt
26
+
27
+ ```bash
28
+ npm install lichta
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 Sử dụng
34
+
35
+ ### Core Logic (mọi framework)
36
+
37
+ Import từ `lichta/core` để sử dụng **pure TypeScript** — không phụ thuộc Svelte:
38
+
39
+ ```typescript
40
+ import { LichTa, getYearDetails } from 'lichta/core';
41
+
42
+ // Dương lịch → Âm lịch
43
+ const lunar = LichTa.toLunar(10, 2, 2024);
44
+ // → {
45
+ // day: 1, month: 1, year: 2024,
46
+ // isLeap: false,
47
+ // yearCanChi: 'Giáp Thìn',
48
+ // monthCanChi: 'Bính Dần',
49
+ // dayCanChi: '...',
50
+ // jd: 2460350
51
+ // }
52
+
53
+ // Âm lịch → Dương lịch
54
+ const solar = LichTa.toSolar(1, 1, 2024, false);
55
+ // → { day: 10, month: 2, year: 2024 }
56
+
57
+ // Múi giờ tùy chỉnh (mặc định GMT+7)
58
+ const lunarChina = LichTa.toLunar(10, 2, 2024, 8); // GMT+8
59
+ ```
60
+
61
+ ### Can Chi & Phong Thủy
62
+
63
+ ```typescript
64
+ import { getYearDetails, getDayCanChi, getMonthCanChi, getHourCanChi, getAuspiciousHours } from 'lichta/core';
65
+
66
+ // Can Chi + Mệnh theo năm
67
+ getYearDetails(2024);
68
+ // → { can: 'Giáp', chi: 'Thìn', menh: 'Hỏa',
69
+ // fullString: 'Giáp Thìn - Mệnh Hỏa' }
70
+
71
+ // Can Chi ngày (cần Julian Day Number)
72
+ const lunar = LichTa.toLunar(10, 2, 2024);
73
+ getDayCanChi(lunar.jd); // → 'Giáp Tý'
74
+
75
+ // Can Chi tháng
76
+ getMonthCanChi(1, 2024); // → tháng Giêng năm Giáp Thìn
77
+
78
+ // Can Chi giờ
79
+ getHourCanChi(8, lunar.jd); // → giờ Thìn (7h-9h)
80
+
81
+ // Giờ Hoàng Đạo
82
+ getAuspiciousHours(lunar.jd);
83
+ // → ['Tý', 'Sửu', 'Mão', 'Ngọ', 'Mùi', 'Dậu']
84
+ ```
85
+
86
+ ### Format & Hiển thị
87
+
88
+ ```typescript
89
+ import { formatTraditional, formatLunarDate, getMonthName, getDayName } from 'lichta/utils';
90
+
91
+ const lunar = LichTa.toLunar(10, 2, 2024);
92
+
93
+ // Format truyền thống
94
+ formatTraditional(lunar);
95
+ // → 'Mùng Một tháng Giêng năm Giáp Thìn'
96
+
97
+ // Format theo pattern
98
+ formatLunarDate(lunar, 'dd/MM/yyyy'); // → '01/01/2024'
99
+ formatLunarDate(lunar, 'CC'); // → 'Giáp Thìn'
100
+ formatLunarDate(lunar, 'Ngày DC'); // → 'Ngày Giáp Tý'
101
+
102
+ // Tên truyền thống
103
+ getMonthName(1); // → 'Giêng'
104
+ getMonthName(12); // → 'Chạp'
105
+ getDayName(1); // → 'Mùng Một'
106
+ getDayName(15); // → 'Rằm'
107
+ getDayName(30); // → 'Ba Mươi'
108
+ ```
109
+
110
+ **Format tokens:**
111
+
112
+ | Token | Mô tả | Ví dụ |
113
+ |--------|-------------------------------|--------------|
114
+ | `dd` | Ngày 2 chữ số | 01, 15 |
115
+ | `d` | Ngày | 1, 15 |
116
+ | `MM` | Tháng 2 chữ số | 01, 12 |
117
+ | `M` | Tháng | 1, 12 |
118
+ | `yyyy` | Năm 4 chữ số | 2024 |
119
+ | `yy` | Năm 2 chữ số cuối | 24 |
120
+ | `CC` | Can Chi năm | Giáp Thìn |
121
+ | `DC` | Can Chi ngày | Giáp Tý |
122
+ | `MC` | Can Chi tháng | Bính Dần |
123
+ | `L` | "Nhuận" nếu tháng nhuận | Nhuận |
124
+
125
+ ### Svelte 5 Components
126
+
127
+ ```svelte
128
+ <script>
129
+ import { Calendar, Formatter } from 'lichta';
130
+ </script>
131
+
132
+ <!-- Lịch tháng đầy đủ -->
133
+ <Calendar
134
+ showLunar={true}
135
+ locale="vi"
136
+ onSelect={(date, lunar) => console.log(date, lunar)}
137
+ />
138
+
139
+ <!-- Hiển thị ngày âm lịch -->
140
+ <Formatter date={new Date()} />
141
+ ```
142
+
143
+ #### Calendar Props
144
+
145
+ | Prop | Type | Mặc định | Mô tả |
146
+ |----------------|-----------------------------------------------|-------------------|-------------------------------|
147
+ | `month` | `number` | Tháng hiện tại | Tháng hiển thị (1-12) |
148
+ | `year` | `number` | Năm hiện tại | Năm hiển thị |
149
+ | `selectedDate` | `Date \| null` | `null` | Ngày được chọn |
150
+ | `onSelect` | `(date: Date, lunar: LunarDate) => void` | — | Callback khi chọn ngày |
151
+ | `showLunar` | `boolean` | `true` | Hiển thị ngày âm lịch |
152
+ | `locale` | `'vi' \| 'en' \| 'ja' \| 'ko'` | `'vi'` | Ngôn ngữ |
153
+ | `dayCell` | `Snippet` | — | Custom snippet cho ô ngày |
154
+
155
+ #### Theming
156
+
157
+ Calendar hỗ trợ CSS custom properties:
158
+
159
+ ```css
160
+ :root {
161
+ --lichta-primary: #d4a373;
162
+ --lichta-bg: #fffcf7;
163
+ --lichta-text: #2c1810;
164
+ --lichta-today-bg: #d4a373;
165
+ --lichta-selected-bg: #a0522d;
166
+ --lichta-lunar-text: #b08968;
167
+ --lichta-radius: 8px;
168
+ --lichta-font: 'Inter', sans-serif;
169
+ }
170
+ ```
171
+
172
+ ### i18n
173
+
174
+ ```typescript
175
+ import { t, getZodiacAnimal } from 'lichta';
176
+
177
+ // Lấy bộ dịch
178
+ const vi = t('vi');
179
+ vi.heavenlyStems; // ['Giáp', 'Ất', ...]
180
+ vi.earthlyBranches; // ['Tý', 'Sửu', ...]
181
+ vi.fiveElements; // ['Kim', 'Mộc', 'Thủy', 'Hỏa', 'Thổ']
182
+ vi.monthNames; // ['Giêng', 'Hai', ..., 'Chạp']
183
+
184
+ const en = t('en');
185
+ en.fiveElements; // ['Metal', 'Wood', 'Water', 'Fire', 'Earth']
186
+ en.zodiacAnimals; // ['Rat', 'Ox', 'Tiger', ...]
187
+
188
+ const ja = t('ja');
189
+ ja.weekDays; // ['日', '月', '火', '水', '木', '金', '土']
190
+
191
+ const ko = t('ko');
192
+ ko.fiveElements; // ['금', '목', '수', '화', '토']
193
+
194
+ // Con giáp theo năm
195
+ getZodiacAnimal(0, 'vi'); // → 'Chuột'
196
+ getZodiacAnimal(4, 'en'); // → 'Dragon'
197
+ getZodiacAnimal(0, 'ja'); // → '鼠'
198
+ ```
199
+
200
+ ---
201
+
202
+ ## 📁 Import Paths
203
+
204
+ | Path | Nội dung | Dùng cho |
205
+ |-----------------|-----------------------------------|-------------------------------|
206
+ | `lichta` | Toàn bộ API + Svelte components | Svelte 5 projects |
207
+ | `lichta/core` | Pure TypeScript logic | React, Vue, Angular, vanilla |
208
+ | `lichta/utils` | Format utilities | Mọi framework |
209
+
210
+ ```typescript
211
+ // Svelte 5 — import tất cả
212
+ import { LichTa, Calendar, Formatter, formatTraditional } from 'lichta';
213
+
214
+ // React / Vue / Angular — chỉ import core logic
215
+ import { LichTa, getYearDetails } from 'lichta/core';
216
+ import { formatTraditional, getDayName } from 'lichta/utils';
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 📐 Thuật toán
222
+
223
+ LichTa sử dụng thuật toán chuyển đổi âm lịch của **Hồ Ngọc Đức** (Đại học Leipzig), dựa trên:
224
+
225
+ - **Julian Day Number** — Hệ tọa độ ngày liên tục cho tính toán thiên văn
226
+ - **Điểm Sóc (New Moon)** — Xác định mùng 1 âm lịch
227
+ - **Trung Khí (Solar Terms)** — Xác định tháng và tháng nhuận
228
+ - **Jean Meeus, "Astronomical Algorithms"** — Công thức thiên văn chính xác
229
+
230
+ **Phạm vi**: 1800 – 2199 | **Múi giờ mặc định**: GMT+7 (Việt Nam)
231
+
232
+ Xem chi tiết trong [docs/ALGORITHM.md](./docs/ALGORITHM.md).
233
+
234
+ ---
235
+
236
+ ## 📄 License
237
+
238
+ [MIT](./LICENSE)
239
+
240
+ ---
241
+
242
+ ## 🙏 Credits
243
+
244
+ - **Hồ Ngọc Đức** — Thuật toán gốc ([informatik.uni-leipzig.de](http://www.informatik.uni-leipzig.de/~duc/amlich/))
245
+ - **Jean Meeus** — *Astronomical Algorithms* (1998)
@@ -0,0 +1,437 @@
1
+ <script lang="ts">
2
+ import { LichTa } from '../core/lunar.js';
3
+ import { getYearDetails } from '../core/feng-shui.js';
4
+ import type { LunarDate } from '../core/types.js';
5
+ import type { Snippet } from 'svelte';
6
+
7
+ import type { Locale } from '../constants/i18n.js';
8
+
9
+ /**
10
+ * Dữ liệu cho mỗi ô ngày trong lưới lịch
11
+ */
12
+ interface DayCellData {
13
+ solar: Date;
14
+ lunar: LunarDate;
15
+ isToday: boolean;
16
+ isSelected: boolean;
17
+ isCurrentMonth: boolean;
18
+ }
19
+
20
+ interface Props {
21
+ /** Tháng hiển thị (1-12). Mặc định: tháng hiện tại */
22
+ month?: number;
23
+ /** Năm hiển thị. Mặc định: năm hiện tại */
24
+ year?: number;
25
+ /** Ngày được chọn */
26
+ selectedDate?: Date | null;
27
+ /** Callback khi chọn ngày */
28
+ onSelect?: (date: Date, lunar: LunarDate) => void;
29
+ /** Hiển thị ngày âm lịch bên dưới ngày dương */
30
+ showLunar?: boolean;
31
+ /** Locale (vi | en | ja | ko) */
32
+ locale?: Locale;
33
+ /** Custom snippet cho mỗi ô ngày */
34
+ dayCell?: Snippet<[DayCellData]>;
35
+ children?: Snippet;
36
+ }
37
+
38
+ let {
39
+ month = new Date().getMonth() + 1,
40
+ year = new Date().getFullYear(),
41
+ selectedDate = null,
42
+ onSelect,
43
+ showLunar = true,
44
+ locale = 'vi',
45
+ dayCell,
46
+ children,
47
+ }: Props = $props();
48
+
49
+ // State nội bộ cho navigation (khởi tạo từ props hoặc giá trị mặc định)
50
+ let currentMonth = $state(month ?? new Date().getMonth() + 1);
51
+ let currentYear = $state(year ?? new Date().getFullYear());
52
+
53
+ // Tên thứ trong tuần
54
+ const weekDayLabels = $derived(
55
+ locale === 'vi'
56
+ ? ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7']
57
+ : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
58
+ );
59
+
60
+ // Tên tháng hiển thị
61
+ const monthNames = [
62
+ 'Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6',
63
+ 'Tháng 7', 'Tháng 8', 'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12',
64
+ ];
65
+
66
+ // Can Chi năm hiển thị
67
+ let yearInfo = $derived(getYearDetails(currentYear));
68
+
69
+ // Ngày hôm nay
70
+ const today = new Date();
71
+ const todayStr = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
72
+
73
+ // Tính grid lịch 6x7 = 42 ô
74
+ let calendarGrid = $derived.by((): DayCellData[] => {
75
+ const grid: DayCellData[] = [];
76
+
77
+ // Ngày đầu tiên của tháng
78
+ const firstDay = new Date(currentYear, currentMonth - 1, 1);
79
+ // Thứ của ngày đầu tiên (0=CN, 1=T2, ...)
80
+ const startDayOfWeek = firstDay.getDay();
81
+ // Số ngày trong tháng
82
+ const daysInMonth = new Date(currentYear, currentMonth, 0).getDate();
83
+
84
+ // Padding ngày tháng trước
85
+ const prevMonthDays = new Date(currentYear, currentMonth - 1, 0).getDate();
86
+ for (let i = startDayOfWeek - 1; i >= 0; i--) {
87
+ const d = prevMonthDays - i;
88
+ const m = currentMonth - 1 <= 0 ? 12 : currentMonth - 1;
89
+ const y = currentMonth - 1 <= 0 ? currentYear - 1 : currentYear;
90
+ const solar = new Date(y, m - 1, d);
91
+ const lunar = LichTa.toLunar(d, m, y);
92
+ const dateStr = `${y}-${m}-${d}`;
93
+ const selStr = selectedDate
94
+ ? `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`
95
+ : '';
96
+
97
+ grid.push({
98
+ solar,
99
+ lunar,
100
+ isToday: dateStr === todayStr,
101
+ isSelected: dateStr === selStr,
102
+ isCurrentMonth: false,
103
+ });
104
+ }
105
+
106
+ // Ngày trong tháng hiện tại
107
+ for (let d = 1; d <= daysInMonth; d++) {
108
+ const solar = new Date(currentYear, currentMonth - 1, d);
109
+ const lunar = LichTa.toLunar(d, currentMonth, currentYear);
110
+ const dateStr = `${currentYear}-${currentMonth}-${d}`;
111
+ const selStr = selectedDate
112
+ ? `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`
113
+ : '';
114
+
115
+ grid.push({
116
+ solar,
117
+ lunar,
118
+ isToday: dateStr === todayStr,
119
+ isSelected: dateStr === selStr,
120
+ isCurrentMonth: true,
121
+ });
122
+ }
123
+
124
+ // Padding ngày tháng sau
125
+ const remaining = 42 - grid.length;
126
+ for (let d = 1; d <= remaining; d++) {
127
+ const m = currentMonth + 1 > 12 ? 1 : currentMonth + 1;
128
+ const y = currentMonth + 1 > 12 ? currentYear + 1 : currentYear;
129
+ const solar = new Date(y, m - 1, d);
130
+ const lunar = LichTa.toLunar(d, m, y);
131
+ const dateStr = `${y}-${m}-${d}`;
132
+ const selStr = selectedDate
133
+ ? `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`
134
+ : '';
135
+
136
+ grid.push({
137
+ solar,
138
+ lunar,
139
+ isToday: dateStr === todayStr,
140
+ isSelected: dateStr === selStr,
141
+ isCurrentMonth: false,
142
+ });
143
+ }
144
+
145
+ return grid;
146
+ });
147
+
148
+ // Navigation
149
+ function prevMonth() {
150
+ if (currentMonth === 1) {
151
+ currentMonth = 12;
152
+ currentYear -= 1;
153
+ } else {
154
+ currentMonth -= 1;
155
+ }
156
+ }
157
+
158
+ function nextMonth() {
159
+ if (currentMonth === 12) {
160
+ currentMonth = 1;
161
+ currentYear += 1;
162
+ } else {
163
+ currentMonth += 1;
164
+ }
165
+ }
166
+
167
+ function handleDayClick(cell: DayCellData) {
168
+ onSelect?.(cell.solar, cell.lunar);
169
+ }
170
+
171
+ function handleKeydown(event: KeyboardEvent, cell: DayCellData) {
172
+ if (event.key === 'Enter' || event.key === ' ') {
173
+ event.preventDefault();
174
+ handleDayClick(cell);
175
+ }
176
+ }
177
+ </script>
178
+
179
+ <div class="lich-ta-calendar">
180
+ <!-- Header: Navigation + Tháng/Năm -->
181
+ <div class="lich-ta-calendar-header">
182
+ <button class="lich-ta-calendar-nav" onclick={prevMonth} aria-label="Tháng trước">◀</button>
183
+ <div class="lich-ta-calendar-title">
184
+ <span class="lich-ta-calendar-month-year">
185
+ {monthNames[currentMonth - 1]}, {currentYear}
186
+ </span>
187
+ <span class="lich-ta-calendar-canchi">
188
+ {yearInfo.can} {yearInfo.chi}
189
+ </span>
190
+ </div>
191
+ <button class="lich-ta-calendar-nav" onclick={nextMonth} aria-label="Tháng sau">▶</button>
192
+ </div>
193
+
194
+ <!-- Tên thứ -->
195
+ <div class="lich-ta-calendar-weekdays">
196
+ {#each weekDayLabels as label}
197
+ <div class="lich-ta-calendar-weekday">{label}</div>
198
+ {/each}
199
+ </div>
200
+
201
+ <!-- Grid ngày -->
202
+ <div class="lich-ta-calendar-grid">
203
+ {#each calendarGrid as cell}
204
+ {#if dayCell}
205
+ {@render dayCell(cell)}
206
+ {:else}
207
+ <button
208
+ class="lich-ta-calendar-day"
209
+ class:is-today={cell.isToday}
210
+ class:is-selected={cell.isSelected}
211
+ class:is-other-month={!cell.isCurrentMonth}
212
+ class:is-first-lunar={cell.lunar.day === 1}
213
+ onclick={() => handleDayClick(cell)}
214
+ onkeydown={(e) => handleKeydown(e, cell)}
215
+ tabindex={cell.isCurrentMonth ? 0 : -1}
216
+ >
217
+ <span class="solar-day">{cell.solar.getDate()}</span>
218
+ {#if showLunar}
219
+ <span class="lunar-day">
220
+ {#if cell.lunar.day === 1}
221
+ {cell.lunar.day}/{cell.lunar.month}{cell.lunar.isLeap ? '*' : ''}
222
+ {:else}
223
+ {cell.lunar.day}
224
+ {/if}
225
+ </span>
226
+ {/if}
227
+ </button>
228
+ {/if}
229
+ {/each}
230
+ </div>
231
+
232
+ <!-- Slot cho nội dung tùy chỉnh bên dưới lịch -->
233
+ {#if children}
234
+ <div class="lich-ta-calendar-footer">
235
+ {@render children()}
236
+ </div>
237
+ {/if}
238
+ </div>
239
+
240
+ <style>
241
+ .lich-ta-calendar {
242
+ --lichta-primary: #d4a373;
243
+ --lichta-primary-light: #f5e6d3;
244
+ --lichta-bg: #fffcf7;
245
+ --lichta-surface: #ffffff;
246
+ --lichta-text: #2c1810;
247
+ --lichta-text-muted: #8b7355;
248
+ --lichta-text-dim: #c4b5a0;
249
+ --lichta-border: #e8ddd0;
250
+ --lichta-today-bg: #d4a373;
251
+ --lichta-today-text: #ffffff;
252
+ --lichta-selected-bg: #a0522d;
253
+ --lichta-selected-text: #ffffff;
254
+ --lichta-hover-bg: #f5e6d3;
255
+ --lichta-lunar-text: #b08968;
256
+ --lichta-first-lunar: #c2185b;
257
+ --lichta-radius: 8px;
258
+ --lichta-font: inherit;
259
+
260
+ font-family: var(--lichta-font);
261
+ background: var(--lichta-bg);
262
+ border: 1px solid var(--lichta-border);
263
+ border-radius: calc(var(--lichta-radius) * 2);
264
+ padding: 16px;
265
+ max-width: 420px;
266
+ width: 100%;
267
+ box-sizing: border-box;
268
+ user-select: none;
269
+ }
270
+
271
+ /* Header */
272
+ .lich-ta-calendar-header {
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: space-between;
276
+ margin-bottom: 16px;
277
+ }
278
+
279
+ .lich-ta-calendar-nav {
280
+ background: none;
281
+ border: 1px solid var(--lichta-border);
282
+ border-radius: var(--lichta-radius);
283
+ width: 36px;
284
+ height: 36px;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ cursor: pointer;
289
+ color: var(--lichta-text);
290
+ font-size: 0.85rem;
291
+ transition: all 0.15s ease;
292
+ }
293
+
294
+ .lich-ta-calendar-nav:hover {
295
+ background: var(--lichta-hover-bg);
296
+ border-color: var(--lichta-primary);
297
+ }
298
+
299
+ .lich-ta-calendar-title {
300
+ text-align: center;
301
+ }
302
+
303
+ .lich-ta-calendar-month-year {
304
+ display: block;
305
+ font-size: 1.1rem;
306
+ font-weight: 600;
307
+ color: var(--lichta-text);
308
+ }
309
+
310
+ .lich-ta-calendar-canchi {
311
+ display: block;
312
+ font-size: 0.8rem;
313
+ color: var(--lichta-text-muted);
314
+ margin-top: 2px;
315
+ }
316
+
317
+ /* Weekdays */
318
+ .lich-ta-calendar-weekdays {
319
+ display: grid;
320
+ grid-template-columns: repeat(7, 1fr);
321
+ margin-bottom: 4px;
322
+ }
323
+
324
+ .lich-ta-calendar-weekday {
325
+ text-align: center;
326
+ font-size: 0.75rem;
327
+ font-weight: 600;
328
+ color: var(--lichta-text-muted);
329
+ padding: 6px 0;
330
+ text-transform: uppercase;
331
+ letter-spacing: 0.05em;
332
+ }
333
+
334
+ /* Grid */
335
+ .lich-ta-calendar-grid {
336
+ display: grid;
337
+ grid-template-columns: repeat(7, 1fr);
338
+ gap: 2px;
339
+ }
340
+
341
+ .lich-ta-calendar-day {
342
+ display: flex;
343
+ flex-direction: column;
344
+ align-items: center;
345
+ justify-content: center;
346
+ padding: 6px 2px;
347
+ min-height: 48px;
348
+ border: none;
349
+ border-radius: var(--lichta-radius);
350
+ background: transparent;
351
+ cursor: pointer;
352
+ transition: all 0.15s ease;
353
+ font-family: inherit;
354
+ }
355
+
356
+ .lich-ta-calendar-day:hover {
357
+ background: var(--lichta-hover-bg);
358
+ }
359
+
360
+ .lich-ta-calendar-day:focus-visible {
361
+ outline: 2px solid var(--lichta-primary);
362
+ outline-offset: 1px;
363
+ }
364
+
365
+ .solar-day {
366
+ font-size: 0.95rem;
367
+ font-weight: 500;
368
+ color: var(--lichta-text);
369
+ line-height: 1.2;
370
+ }
371
+
372
+ .lunar-day {
373
+ font-size: 0.65rem;
374
+ color: var(--lichta-lunar-text);
375
+ line-height: 1.2;
376
+ margin-top: 1px;
377
+ }
378
+
379
+ /* States */
380
+ .is-other-month .solar-day {
381
+ color: var(--lichta-text-dim);
382
+ }
383
+
384
+ .is-other-month .lunar-day {
385
+ color: var(--lichta-text-dim);
386
+ }
387
+
388
+ .is-today {
389
+ background: var(--lichta-today-bg);
390
+ }
391
+
392
+ .is-today .solar-day {
393
+ color: var(--lichta-today-text);
394
+ font-weight: 700;
395
+ }
396
+
397
+ .is-today .lunar-day {
398
+ color: var(--lichta-today-text);
399
+ opacity: 0.85;
400
+ }
401
+
402
+ .is-today:hover {
403
+ background: var(--lichta-today-bg);
404
+ opacity: 0.9;
405
+ }
406
+
407
+ .is-selected {
408
+ background: var(--lichta-selected-bg);
409
+ }
410
+
411
+ .is-selected .solar-day {
412
+ color: var(--lichta-selected-text);
413
+ font-weight: 700;
414
+ }
415
+
416
+ .is-selected .lunar-day {
417
+ color: var(--lichta-selected-text);
418
+ opacity: 0.85;
419
+ }
420
+
421
+ .is-first-lunar .lunar-day {
422
+ color: var(--lichta-first-lunar);
423
+ font-weight: 600;
424
+ }
425
+
426
+ .is-today.is-first-lunar .lunar-day,
427
+ .is-selected.is-first-lunar .lunar-day {
428
+ color: var(--lichta-today-text);
429
+ }
430
+
431
+ /* Footer */
432
+ .lich-ta-calendar-footer {
433
+ margin-top: 12px;
434
+ padding-top: 12px;
435
+ border-top: 1px solid var(--lichta-border);
436
+ }
437
+ </style>
@@ -0,0 +1,33 @@
1
+ import type { LunarDate } from '../core/types.js';
2
+ import type { Snippet } from 'svelte';
3
+ import type { Locale } from '../constants/i18n.js';
4
+ /**
5
+ * Dữ liệu cho mỗi ô ngày trong lưới lịch
6
+ */
7
+ interface DayCellData {
8
+ solar: Date;
9
+ lunar: LunarDate;
10
+ isToday: boolean;
11
+ isSelected: boolean;
12
+ isCurrentMonth: boolean;
13
+ }
14
+ interface Props {
15
+ /** Tháng hiển thị (1-12). Mặc định: tháng hiện tại */
16
+ month?: number;
17
+ /** Năm hiển thị. Mặc định: năm hiện tại */
18
+ year?: number;
19
+ /** Ngày được chọn */
20
+ selectedDate?: Date | null;
21
+ /** Callback khi chọn ngày */
22
+ onSelect?: (date: Date, lunar: LunarDate) => void;
23
+ /** Hiển thị ngày âm lịch bên dưới ngày dương */
24
+ showLunar?: boolean;
25
+ /** Locale (vi | en | ja | ko) */
26
+ locale?: Locale;
27
+ /** Custom snippet cho mỗi ô ngày */
28
+ dayCell?: Snippet<[DayCellData]>;
29
+ children?: Snippet;
30
+ }
31
+ declare const Calendar: import("svelte").Component<Props, {}, "">;
32
+ type Calendar = ReturnType<typeof Calendar>;
33
+ export default Calendar;