@reachweb/alpine-calendar 0.1.1

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.
@@ -0,0 +1,506 @@
1
+ import { CalendarDate } from '../core/calendar-date';
2
+ import type { ConstraintMessages } from '../core/constraints';
3
+ import type { DayCell, MonthGrid, MonthCell, YearCell } from '../core/grid';
4
+ import type { Selection } from '../core/selection';
5
+ import type { RangePreset } from '../core/presets';
6
+ /** Period-specific constraint rule (string-based for Alpine/HTML config). */
7
+ export interface CalendarConfigRule {
8
+ /** Start of the period (ISO string). Required unless `months` is set. */
9
+ from?: string;
10
+ /** End of the period (ISO string). Required unless `months` is set. */
11
+ to?: string;
12
+ /** Recurring months this rule applies to (1=Jan, …, 12=Dec). Matches every year. */
13
+ months?: number[];
14
+ /** Priority for conflict resolution. Higher values win. Default: 0. */
15
+ priority?: number;
16
+ /** Minimum selectable date within this period (ISO string). */
17
+ minDate?: string;
18
+ /** Maximum selectable date within this period (ISO string). */
19
+ maxDate?: string;
20
+ /** Specific dates to disable within this period (ISO strings). */
21
+ disabledDates?: string[];
22
+ /** Days of the week to disable within this period (0=Sun, 6=Sat). */
23
+ disabledDaysOfWeek?: number[];
24
+ /** Specific dates to force-enable within this period (ISO strings). */
25
+ enabledDates?: string[];
26
+ /** Days of the week to enable within this period (whitelist, 0=Sun, 6=Sat). */
27
+ enabledDaysOfWeek?: number[];
28
+ /** Specific months to disable within this period (1=Jan, 12=Dec). */
29
+ disabledMonths?: number[];
30
+ /** Months to enable within this period (whitelist, 1=Jan, 12=Dec). */
31
+ enabledMonths?: number[];
32
+ /** Specific years to disable within this period. */
33
+ disabledYears?: number[];
34
+ /** Years to enable within this period (whitelist). */
35
+ enabledYears?: number[];
36
+ /** Minimum range length in days (inclusive) for this period. */
37
+ minRange?: number;
38
+ /** Maximum range length in days (inclusive) for this period. */
39
+ maxRange?: number;
40
+ }
41
+ export type { RangePreset } from '../core/presets';
42
+ export interface CalendarConfig {
43
+ /** Selection mode. Default: 'single'. */
44
+ mode?: 'single' | 'multiple' | 'range';
45
+ /** Display mode. Default: 'inline'. */
46
+ display?: 'inline' | 'popup';
47
+ /** Date format string (e.g. 'DD/MM/YYYY'). Default: 'DD/MM/YYYY'. */
48
+ format?: string;
49
+ /** Number of months to show. 1 = single, 2 = side-by-side dual, 3+ = scrollable. Default: 1. */
50
+ months?: number;
51
+ /** Max height of the scrollable container in px (only used when months >= 3). Default: 400. */
52
+ scrollHeight?: number;
53
+ /** First day of the week (0=Sun, 1=Mon, …, 6=Sat). Default: 0. */
54
+ firstDay?: number;
55
+ /** Minimum selectable date (ISO string). */
56
+ minDate?: string;
57
+ /** Maximum selectable date (ISO string). */
58
+ maxDate?: string;
59
+ /** Specific dates to disable (ISO strings). */
60
+ disabledDates?: string[];
61
+ /** Days of the week to disable (0=Sun, 6=Sat). */
62
+ disabledDaysOfWeek?: number[];
63
+ /** Specific dates to force-enable (ISO strings). Overrides disabled checks. */
64
+ enabledDates?: string[];
65
+ /** Days of the week to enable (whitelist — all other days disabled). */
66
+ enabledDaysOfWeek?: number[];
67
+ /** Specific months to disable (1=Jan, 12=Dec). */
68
+ disabledMonths?: number[];
69
+ /** Months to enable (whitelist — all other months disabled, 1=Jan, 12=Dec). */
70
+ enabledMonths?: number[];
71
+ /** Specific years to disable. */
72
+ disabledYears?: number[];
73
+ /** Years to enable (whitelist — all other years disabled). */
74
+ enabledYears?: number[];
75
+ /** Minimum range length in days (inclusive). Only used in range mode. */
76
+ minRange?: number;
77
+ /** Maximum range length in days (inclusive). Only used in range mode. */
78
+ maxRange?: number;
79
+ /** Period-specific constraint rules. Highest priority matching rule wins; ties broken by array order. */
80
+ rules?: CalendarConfigRule[];
81
+ /** Enable birth-date wizard mode. Default: false. Use 'year-month' or 'month-day' for half-wizard. */
82
+ wizard?: boolean | 'year-month' | 'month-day';
83
+ /** Enable input masking. Default: true. */
84
+ mask?: boolean;
85
+ /** IANA timezone for "today" resolution. */
86
+ timezone?: string;
87
+ /** BCP 47 locale string. */
88
+ locale?: string;
89
+ /** Initial value (ISO string or formatted string). */
90
+ value?: string;
91
+ /** Name attribute for hidden form input(s). */
92
+ name?: string;
93
+ /** ID attribute for the popup input element. Allows external `<label for="...">` association. */
94
+ inputId?: string;
95
+ /** Alpine x-ref name for the input element. Default: 'rc-input'. */
96
+ inputRef?: string;
97
+ /** Show ISO 8601 week numbers on the left side of the day grid. Default: false. */
98
+ showWeekNumbers?: boolean;
99
+ /**
100
+ * Predefined date range presets (e.g., "Today", "Last 7 Days", "This Month").
101
+ * Displayed as quick-select buttons alongside the calendar. Only meaningful in range or single mode.
102
+ */
103
+ presets?: RangePreset[];
104
+ /** Close popup after a date is selected. Default: true. */
105
+ closeOnSelect?: boolean;
106
+ /**
107
+ * Callback invoked before a date is selected. Return `false` to prevent the selection.
108
+ *
109
+ * Receives the date being selected and context about the current selection state.
110
+ * Called after built-in constraint checks (disabled dates, range validation) pass.
111
+ *
112
+ * Usage:
113
+ * ```js
114
+ * calendar({
115
+ * beforeSelect(date, { mode, selectedDates, action }) {
116
+ * // Prevent selecting more than 5 dates
117
+ * if (mode === 'multiple' && action === 'select' && selectedDates.length >= 5) return false
118
+ * return true
119
+ * }
120
+ * })
121
+ * ```
122
+ */
123
+ beforeSelect?: (date: CalendarDate, context: {
124
+ /** Current selection mode. */
125
+ mode: 'single' | 'multiple' | 'range';
126
+ /** Currently selected dates (before this action). */
127
+ selectedDates: CalendarDate[];
128
+ /** Whether this click would select or deselect the date. */
129
+ action: 'select' | 'deselect';
130
+ }) => boolean | undefined;
131
+ /** Custom messages for disabled-date tooltips. Overrides default English strings. */
132
+ constraintMessages?: ConstraintMessages;
133
+ /** Auto-render template when no `.rc-calendar` exists in the container. Default: true. Set false to require manual template. */
134
+ template?: boolean;
135
+ }
136
+ /** Constraint-related keys from CalendarConfig. */
137
+ declare const CONSTRAINT_KEYS: readonly ["minDate", "maxDate", "disabledDates", "disabledDaysOfWeek", "enabledDates", "enabledDaysOfWeek", "disabledMonths", "enabledMonths", "disabledYears", "enabledYears", "minRange", "maxRange", "rules"];
138
+ type ConstraintConfig = Pick<CalendarConfig, (typeof CONSTRAINT_KEYS)[number]>;
139
+ /**
140
+ * Create the Alpine component data object for the calendar.
141
+ *
142
+ * This factory is called by `Alpine.data('calendar', createCalendarData)`.
143
+ * The returned object becomes the reactive component state.
144
+ */
145
+ export declare function createCalendarData(config?: CalendarConfig, Alpine?: {
146
+ initTree: (el: HTMLElement) => void;
147
+ }): {
148
+ mode: "single" | "multiple" | "range";
149
+ display: "inline" | "popup";
150
+ format: string;
151
+ monthCount: number;
152
+ firstDay: number;
153
+ wizard: boolean;
154
+ wizardMode: "year-month" | "month-day" | "none" | "full";
155
+ wizardTotalSteps: number;
156
+ showWeekNumbers: boolean;
157
+ presets: RangePreset[];
158
+ inputName: string;
159
+ month: number;
160
+ year: number;
161
+ view: "days" | "months" | "years";
162
+ isOpen: boolean;
163
+ grid: MonthGrid[];
164
+ monthGrid: MonthCell[][];
165
+ yearGrid: YearCell[][];
166
+ inputValue: string;
167
+ popupStyle: string;
168
+ focusedDate: CalendarDate | null;
169
+ hoverDate: CalendarDate | null;
170
+ wizardStep: number;
171
+ _wizardYear: number | null;
172
+ _wizardMonth: number | null;
173
+ _wizardDay: number | null;
174
+ _navDirection: "" | "next" | "prev";
175
+ _selection: Selection;
176
+ _today: CalendarDate;
177
+ _constraintConfig: ConstraintConfig;
178
+ _isDisabledDate: (d: CalendarDate) => boolean;
179
+ _isRangeValid: (start: CalendarDate, end: CalendarDate) => boolean;
180
+ _isMonthDisabled: (year: number, month: number) => boolean;
181
+ _isYearDisabled: (year: number) => boolean;
182
+ _getDisabledReasons: (d: CalendarDate) => string[];
183
+ _inputEl: HTMLInputElement | null;
184
+ _detachInput: (() => void) | null;
185
+ _syncing: boolean;
186
+ _suppressFocusOpen: boolean;
187
+ _Alpine: {
188
+ initTree: (el: HTMLElement) => void;
189
+ } | null;
190
+ _autoRendered: boolean;
191
+ isScrollable: boolean;
192
+ _scrollHeight: number;
193
+ _scrollContainerEl: HTMLElement | null;
194
+ _scrollObserver: IntersectionObserver | null;
195
+ /** Index into grid[] of the month currently visible at top of scroll viewport. */
196
+ _scrollVisibleIndex: number;
197
+ /**
198
+ * Reactive counter bumped on selection changes. Read by dayClasses() so Alpine
199
+ * re-evaluates class bindings without needing a full grid rebuild.
200
+ */
201
+ _selectionRev: number;
202
+ _statusMessage: string;
203
+ readonly selectedDates: CalendarDate[];
204
+ readonly formattedValue: string;
205
+ /** ISO string values for hidden form inputs. */
206
+ readonly hiddenInputValues: string[];
207
+ /** ISO string of focused date for aria-activedescendant binding. */
208
+ readonly focusedDateISO: string;
209
+ /** ID for the popup input element (for external label association). */
210
+ readonly inputId: string | null;
211
+ /** Accessible label for the popup input element. */
212
+ readonly inputAriaLabel: string;
213
+ /** Accessible label for the popup dialog. */
214
+ readonly popupAriaLabel: string;
215
+ /** Label for the current wizard step (e.g. "Select Year"). */
216
+ readonly wizardStepLabel: string;
217
+ /** Summary of wizard selections so far (e.g. "1995 · June · 15"). */
218
+ readonly wizardSummary: string;
219
+ /**
220
+ * Localized short weekday headers in the correct order for the current `firstDay`.
221
+ * Returns an array of 7 short names (e.g., ["Mo", "Tu", "We", ...]).
222
+ */
223
+ readonly weekdayHeaders: string[];
224
+ init(): void;
225
+ destroy(): void;
226
+ _rebuildGrid(): void;
227
+ /** Rebuild the 3×4 month picker grid for the current year. */
228
+ _rebuildMonthGrid(): void;
229
+ /** Rebuild the 3×4 year picker grid for the current year's 12-year block. */
230
+ _rebuildYearGrid(): void;
231
+ /**
232
+ * Build a flat array of grid items for template rendering, interleaving week number
233
+ * markers with day cells. Used by the auto-rendered template when `showWeekNumbers` is true.
234
+ */
235
+ dayGridItems(mg: MonthGrid): {
236
+ isWeekNumber: boolean;
237
+ weekNumber: number;
238
+ cell: DayCell;
239
+ key: string;
240
+ }[];
241
+ /** Year label for the month view header (e.g. "2026"). */
242
+ readonly yearLabel: string;
243
+ /** Decade range label for the year view header (e.g. "2016 – 2027"). */
244
+ readonly decadeLabel: string;
245
+ /**
246
+ * Whether backward navigation is possible from the current position.
247
+ *
248
+ * - Days view: checks if the previous month has any selectable dates.
249
+ * - Months view: checks if the previous year has any selectable months.
250
+ * - Years view: checks if the previous 12-year block has any selectable years.
251
+ *
252
+ * Usage in Alpine template:
253
+ * ```html
254
+ * <button @click="prev()" :disabled="!canGoPrev">←</button>
255
+ * ```
256
+ */
257
+ readonly canGoPrev: boolean;
258
+ /**
259
+ * Whether forward navigation is possible from the current position.
260
+ *
261
+ * - Days view: checks if the next month has any selectable dates.
262
+ * - Months view: checks if the next year has any selectable months.
263
+ * - Years view: checks if the next 12-year block has any selectable years.
264
+ *
265
+ * Usage in Alpine template:
266
+ * ```html
267
+ * <button @click="next()" :disabled="!canGoNext">→</button>
268
+ * ```
269
+ */
270
+ readonly canGoNext: boolean;
271
+ /**
272
+ * Compute CSS class object for a year cell in the year picker view.
273
+ */
274
+ yearClasses(cell: YearCell): Record<string, boolean>;
275
+ /**
276
+ * Compute CSS class object for a month cell in the month picker view.
277
+ */
278
+ monthClasses(cell: MonthCell): Record<string, boolean>;
279
+ /**
280
+ * Get a localized "Month Year" label for a specific month grid.
281
+ * @param gridIndex - Index into the `grid` array (0 for first month, 1 for second).
282
+ */
283
+ monthYearLabel(gridIndex: number): string;
284
+ /**
285
+ * Compute CSS class object for a day cell.
286
+ * Returns an object keyed by class name with boolean values, suitable for Alpine `:class`.
287
+ */
288
+ dayClasses(cell: {
289
+ date: CalendarDate;
290
+ isCurrentMonth: boolean;
291
+ isToday: boolean;
292
+ isDisabled: boolean;
293
+ }): Record<string, boolean>;
294
+ /**
295
+ * Get tooltip text for a day cell explaining why it is disabled.
296
+ * Returns an empty string for enabled dates.
297
+ */
298
+ dayTitle(cell: {
299
+ date: CalendarDate;
300
+ isDisabled: boolean;
301
+ }): string;
302
+ /**
303
+ * Bind an input element to the calendar.
304
+ *
305
+ * Sets up input masking (if enabled), syncs the initial value, and
306
+ * attaches an input listener to keep `inputValue` in sync.
307
+ *
308
+ * Usage:
309
+ * ```html
310
+ * <!-- Alpine component root with calendar() -->
311
+ * <input x-ref="rc-input" type="text">
312
+ * ```
313
+ * The component auto-binds to the ref named by `config.inputRef` (default: `rc-input`) during init().
314
+ * For custom refs, set `inputRef` in config or call `bindInput($refs.myInput)` explicitly.
315
+ */
316
+ bindInput(el: HTMLInputElement): void;
317
+ /**
318
+ * Handle input event for unbound inputs (using `:value` + `@input`).
319
+ * When using `bindInput()`, this is handled automatically.
320
+ */
321
+ handleInput(e: Event): void;
322
+ /**
323
+ * Handle focus on the input element.
324
+ * Opens the calendar popup in popup display mode.
325
+ */
326
+ handleFocus(): void;
327
+ /**
328
+ * Handle blur on the input element.
329
+ * Parses the typed value, updates selection if valid, and reformats the input.
330
+ */
331
+ handleBlur(): void;
332
+ /** Update inputValue and bound input element from current selection. */
333
+ _syncInputFromSelection(): void;
334
+ /** Announce a message to screen readers via the aria-live status region. */
335
+ _announce(message: string): void;
336
+ /** Announce the current navigation context (month/year/decade) for screen readers. */
337
+ _announceNavigation(): void;
338
+ /** Announce the new view after a view switch for screen readers. */
339
+ _announceViewChange(): void;
340
+ /** Dispatch calendar:change event with current selection state. */
341
+ _emitChange(): void;
342
+ /** Dispatch calendar:navigate event on month/year change. */
343
+ _emitNavigate(): void;
344
+ /** Dispatch calendar:view-change event on view switch. */
345
+ _emitViewChange(): void;
346
+ prev(): void;
347
+ next(): void;
348
+ goToToday(): void;
349
+ selectDate(dateOrISO: CalendarDate | string): void;
350
+ isSelected(date: CalendarDate): boolean;
351
+ isInRange(date: CalendarDate, hoverDate?: CalendarDate): boolean;
352
+ isRangeStart(date: CalendarDate): boolean;
353
+ isRangeEnd(date: CalendarDate): boolean;
354
+ /**
355
+ * Check whether selecting `date` as a range endpoint would produce a valid range.
356
+ *
357
+ * Returns `true` when:
358
+ * - The date is not disabled AND
359
+ * - Either no range start is selected yet (any non-disabled date can start), OR
360
+ * - Completing the range with this date would satisfy minRange/maxRange constraints.
361
+ *
362
+ * Returns `false` for non-range modes.
363
+ *
364
+ * Useful for dimming dates in the UI that would result in an invalid range.
365
+ */
366
+ isDateSelectableForRange(date: CalendarDate): boolean;
367
+ /**
368
+ * Get human-readable reasons why a date is disabled.
369
+ * Returns an empty array for enabled dates.
370
+ * Accepts a CalendarDate or ISO string.
371
+ */
372
+ getDisabledReason(date: CalendarDate | string): string[];
373
+ clearSelection(): void;
374
+ /**
375
+ * Set the calendar selection programmatically.
376
+ *
377
+ * Accepts a single ISO date string, an array of ISO strings, or CalendarDate(s).
378
+ * The calendar navigates to show the first selected date.
379
+ *
380
+ * Usage:
381
+ * ```js
382
+ * // Single mode
383
+ * $data.setValue('2025-06-15')
384
+ *
385
+ * // Multiple mode
386
+ * $data.setValue(['2025-06-15', '2025-06-20'])
387
+ *
388
+ * // Range mode
389
+ * $data.setValue(['2025-06-15', '2025-06-20'])
390
+ *
391
+ * // With CalendarDate
392
+ * $data.setValue(new CalendarDate(2025, 6, 15))
393
+ * ```
394
+ */
395
+ setValue(value: string | string[] | CalendarDate | CalendarDate[]): void;
396
+ /**
397
+ * Clear the current selection. Alias for `clearSelection()`.
398
+ */
399
+ clear(): void;
400
+ /**
401
+ * Navigate the calendar to a specific year and month without changing selection.
402
+ *
403
+ * Usage:
404
+ * ```js
405
+ * $data.goTo(2025, 6) // Navigate to June 2025
406
+ * $data.goTo(2030) // Navigate to the current month in 2030
407
+ * ```
408
+ */
409
+ goTo(year: number, month?: number): void;
410
+ /**
411
+ * Get the current selection as an array of CalendarDate objects.
412
+ *
413
+ * Returns a new array each time (safe to mutate).
414
+ *
415
+ * Usage:
416
+ * ```js
417
+ * const dates = $data.getSelection()
418
+ * console.log(dates.map(d => d.toISO()))
419
+ * ```
420
+ */
421
+ getSelection(): CalendarDate[];
422
+ /**
423
+ * Apply a predefined range preset by index.
424
+ *
425
+ * Evaluates the preset's `value()` function, sets the selection to the
426
+ * returned `[start, end]` range, navigates to the start date, and
427
+ * emits a change event. In popup mode with `closeOnSelect`, the popup closes.
428
+ *
429
+ * Usage in Alpine template:
430
+ * ```html
431
+ * <template x-for="(preset, pi) in presets" :key="pi">
432
+ * <button @click="applyPreset(pi)" x-text="preset.label"></button>
433
+ * </template>
434
+ * ```
435
+ */
436
+ applyPreset(index: number): void;
437
+ /**
438
+ * Update constraint-related configuration at runtime.
439
+ *
440
+ * Rebuilds all constraint functions and refreshes grids. Accepts the same
441
+ * constraint properties as `CalendarConfig` (minDate, maxDate, disabledDates,
442
+ * disabledDaysOfWeek, enabledDates, enabledDaysOfWeek, disabledMonths,
443
+ * enabledMonths, disabledYears, enabledYears, minRange, maxRange, rules).
444
+ *
445
+ * Updates are **merged** with the existing constraint config. To remove a
446
+ * previously set constraint, pass `undefined` explicitly (e.g.
447
+ * `{ disabledDaysOfWeek: undefined }`).
448
+ *
449
+ * Usage:
450
+ * ```js
451
+ * $data.updateConstraints({ minDate: '2025-06-01', disabledDaysOfWeek: [0, 6] })
452
+ * ```
453
+ */
454
+ updateConstraints(updates: Partial<CalendarConfig>): void;
455
+ setView(newView: "days" | "months" | "years"): void;
456
+ selectMonth(targetMonth: number): void;
457
+ selectYear(targetYear: number): void;
458
+ /** Navigate the wizard back one step. No-op if not in wizard mode. */
459
+ wizardBack(): void;
460
+ open(): void;
461
+ close(): void;
462
+ toggle(): void;
463
+ /**
464
+ * Handle keydown events on the calendar container.
465
+ *
466
+ * Supports full keyboard navigation per ARIA grid pattern:
467
+ * - Arrow keys: move focus between days (±1 day / ±7 days)
468
+ * - Enter/Space: select the focused day
469
+ * - PageUp/PageDown: navigate prev/next month (+ Shift: prev/next year)
470
+ * - Home/End: jump to first/last day of current month
471
+ * - Escape: return to day view from month/year picker, or close popup
472
+ * - Tab: natural tab order (exits calendar)
473
+ *
474
+ * Uses `aria-activedescendant` pattern: the calendar grid container holds
475
+ * focus (`tabindex="0"`), and `focusedDate` drives which cell is visually
476
+ * highlighted. Day cells should have `id="day-{ISO}"` in the template.
477
+ *
478
+ * Usage in Alpine template:
479
+ * ```html
480
+ * <div @keydown="handleKeydown($event)" tabindex="0"
481
+ * :aria-activedescendant="focusedDateISO ? 'day-' + focusedDateISO : null">
482
+ * ```
483
+ */
484
+ handleKeydown(e: KeyboardEvent): void;
485
+ /**
486
+ * Move focusedDate by a number of days, navigating months as needed.
487
+ * Skips disabled dates in the direction of movement (up to 31 attempts).
488
+ */
489
+ _moveFocus(deltaDays: number): void;
490
+ /**
491
+ * Move focusedDate by a number of months, clamping the day to valid range.
492
+ */
493
+ _moveFocusByMonths(deltaMonths: number): void;
494
+ /**
495
+ * Set focusedDate and navigate the calendar view if needed.
496
+ */
497
+ _setFocusedDate(date: CalendarDate): void;
498
+ /** Label for the sticky header in scrollable mode — tracks the topmost visible month. */
499
+ readonly scrollHeaderLabel: string;
500
+ /** Attach an IntersectionObserver that updates the sticky header as the user scrolls. */
501
+ _initScrollListener(): void;
502
+ /** Disconnect and re-initialize the scroll observer after DOM rebuild. */
503
+ _rebindScrollObserver(): void;
504
+ /** Smooth scroll a specific month into view inside the scroll container. */
505
+ _scrollToMonth(year: number, month: number): void;
506
+ };
@@ -0,0 +1,11 @@
1
+ export interface TemplateOptions {
2
+ display: 'inline' | 'popup';
3
+ isDualMonth: boolean;
4
+ isWizard: boolean;
5
+ hasName: boolean;
6
+ showWeekNumbers: boolean;
7
+ hasPresets: boolean;
8
+ isScrollable: boolean;
9
+ scrollHeight: number;
10
+ }
11
+ export declare function generateCalendarTemplate(options: TemplateOptions): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Lightweight popup positioning engine.
3
+ *
4
+ * Calculates fixed-position coordinates for a floating element relative to
5
+ * a reference element. Supports automatic flipping when the popup would
6
+ * overflow the viewport.
7
+ *
8
+ * No external dependencies — replaces Floating UI for simple use cases.
9
+ */
10
+ export type Placement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
11
+ export interface PositionOptions {
12
+ /** Preferred placement. Default: 'bottom-start'. */
13
+ placement?: Placement;
14
+ /** Offset in pixels between reference and floating element. Default: 4. */
15
+ offset?: number;
16
+ /** Whether to flip to the opposite side when there's not enough space. Default: true. */
17
+ flip?: boolean;
18
+ }
19
+ export interface PositionResult {
20
+ /** X coordinate (CSS `left`) for `position: fixed`. */
21
+ x: number;
22
+ /** Y coordinate (CSS `top`) for `position: fixed`. */
23
+ y: number;
24
+ /** Actual placement after potential flip. */
25
+ placement: Placement;
26
+ }
27
+ /**
28
+ * Compute fixed-position coordinates for a floating element.
29
+ *
30
+ * Uses `getBoundingClientRect()` on both the reference and floating elements
31
+ * to calculate position. The floating element must be in the DOM (but can
32
+ * be hidden with `visibility: hidden`) so its dimensions are measurable.
33
+ *
34
+ * @param reference - The trigger/anchor element
35
+ * @param floating - The popup element to position
36
+ * @param options - Positioning options
37
+ * @returns Position coordinates and resolved placement
38
+ */
39
+ export declare function computePosition(reference: Element, floating: Element, options?: PositionOptions): PositionResult;
40
+ /**
41
+ * Subscribe to scroll and resize events that could affect popup position.
42
+ *
43
+ * Calls `update()` whenever the reference element's position may have
44
+ * changed due to scrolling or window resize. Uses passive event listeners
45
+ * and throttles callbacks for performance.
46
+ *
47
+ * @param reference - The trigger/anchor element
48
+ * @param update - Callback to recalculate position
49
+ * @param throttleMs - Throttle interval in ms. Default: 16 (~60fps)
50
+ * @returns Cleanup function to remove all listeners
51
+ */
52
+ export declare function autoUpdate(reference: Element, update: () => void, throttleMs?: number): () => void;
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@reachweb/alpine-calendar",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "A lightweight, AlpineJS-native calendar component with inline/popup display, input binding with masking, and TailwindCSS 4 theming.",
6
+ "main": "dist/alpine-calendar.umd.js",
7
+ "module": "dist/alpine-calendar.es.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/alpine-calendar.es.js",
13
+ "require": "./dist/alpine-calendar.umd.js"
14
+ },
15
+ "./cdn": "./dist/alpine-calendar.cdn.js",
16
+ "./css": "./dist/alpine-calendar.css"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "sideEffects": [
22
+ "*.css"
23
+ ],
24
+ "scripts": {
25
+ "dev": "vite",
26
+ "build": "pnpm build:lib && pnpm build:cdn && tsc --emitDeclarationOnly",
27
+ "build:lib": "vite build --config vite.config.lib.ts",
28
+ "build:cdn": "vite build --config vite.config.cdn.ts",
29
+ "preview": "vite preview",
30
+ "lint": "eslint src/",
31
+ "lint:fix": "eslint src/ --fix",
32
+ "format": "prettier --write 'src/**/*.{ts,css}'",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "test:coverage": "vitest run --coverage",
36
+ "typecheck": "tsc --noEmit",
37
+ "prepublishOnly": "pnpm run build"
38
+ },
39
+ "peerDependencies": {
40
+ "alpinejs": "^3.0.0"
41
+ },
42
+ "keywords": [
43
+ "alpinejs",
44
+ "calendar",
45
+ "datepicker",
46
+ "tailwindcss",
47
+ "livewire"
48
+ ],
49
+ "license": "MIT",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/reachweb/alpine-calendar"
53
+ },
54
+ "devDependencies": {
55
+ "@eslint/js": "^9.39.3",
56
+ "@tailwindcss/vite": "^4.2.1",
57
+ "@types/alpinejs": "^3.13.11",
58
+ "@vitest/coverage-v8": "^4.0.18",
59
+ "alpinejs": "^3.15.8",
60
+ "axe-core": "^4.11.1",
61
+ "eslint": "^9.39.3",
62
+ "eslint-config-prettier": "^10.1.8",
63
+ "jsdom": "^28.1.0",
64
+ "lint-staged": "^16.2.7",
65
+ "prettier": "^3.8.1",
66
+ "simple-git-hooks": "^2.13.1",
67
+ "tailwindcss": "^4.2.1",
68
+ "terser": "^5.46.0",
69
+ "typescript": "^5.9.3",
70
+ "typescript-eslint": "^8.56.1",
71
+ "vite": "^6.4.1",
72
+ "vitest": "^4.0.18"
73
+ },
74
+ "simple-git-hooks": {
75
+ "pre-commit": "pnpm lint-staged"
76
+ },
77
+ "lint-staged": {
78
+ "*.ts": [
79
+ "eslint --fix",
80
+ "prettier --write"
81
+ ],
82
+ "*.css": [
83
+ "prettier --write"
84
+ ]
85
+ }
86
+ }