@lemonadejs/calendar 5.8.3 → 5.9.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.
package/README.md CHANGED
@@ -1,34 +1,28 @@
1
1
  # JavaScript Calendar
2
2
 
3
- [Official JavaScript Calendar Documenation](https://lemonadejs.com/docs/plugins/calendar)
3
+ [Official JavaScript Calendar Documentation](https://lemonadejs.com/docs/plugins/calendar)
4
4
 
5
5
  Compatible with Vanilla JavaScript, LemonadeJS, React, VueJS or Angular.
6
6
 
7
- # JavaScript Calendar
8
-
9
7
  Leverage the power of the LemonadeJS Calendar, a lightweight and versatile JavaScript component compatible with React, VueJS, and Angular. Designed to enhance web applications, it offers an embeddable calendar for easy date, time, and range selections. Key features include:
10
8
 
11
- - Intuitive keyboard navigation.
12
- - A reactive and responsive design for seamless device adaptation.
13
- - Flexible range selection for various applications.
14
- - Customizable options to match your project needs.
9
+ - Intuitive keyboard navigation.
10
+ - A reactive and responsive design for seamless device adaptation.
11
+ - Flexible range selection for various applications.
12
+ - Customizable options to match your project needs.
15
13
 
16
14
  ## Getting Started
17
15
 
18
- You can install using NPM or using directly from a CDN.
16
+ You can install using NPM or directly from a CDN.
19
17
 
20
18
  ### npm Installation
21
19
 
22
- To install it in your project using npm, run the following command:
23
-
24
20
  ```bash
25
- $ npm install @lemonadejs/calendar
21
+ npm install @lemonadejs/calendar
26
22
  ```
27
23
 
28
24
  ### CDN
29
25
 
30
- For immediate use without NPM, include these script tags in your HTML for access to the calendar and its dependencies:
31
-
32
26
  ```html
33
27
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/modal/dist/style.min.css" />
34
28
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/style.min.css" />
@@ -40,7 +34,7 @@ For immediate use without NPM, include these script tags in your HTML for access
40
34
 
41
35
  ### Usage
42
36
 
43
- Quick example with ReactJS.
37
+ Quick example with React:
44
38
 
45
39
  ```javascript
46
40
  import React, { useRef } from 'react';
@@ -52,39 +46,75 @@ import '@lemonadejs/modal/dist/style.css';
52
46
  export default function App() {
53
47
  const calendarRef = useRef();
54
48
 
55
- return (
56
- <>
57
- <Calendar type={'inline'} ref={calendarRef} value={new Date()} />
58
- </>
59
- );
49
+ return <Calendar type="inline" ref={calendarRef} value={new Date()} />;
60
50
  }
61
51
  ```
62
52
 
63
- ### Configuration
64
-
65
- You can configure things such as calendar starting date, calendar events, and customize functions.
66
-
67
- #### Calendar Properties
68
-
69
- | Attribute | Type | Description |
70
- | --------- | ---------------- | ----------------------------------------------------------------------------------------------------------------- |
71
- | type? | string | Determines the rendering type for the calendar. Options: 'inline', 'default'. |
72
- | range? | boolean | Enables the range mode for date selection. |
73
- | value? | number or string | Represents the currently selected date. |
74
- | numeric? | boolean | Enables the use of numeric dates, treating them as serial numbers. |
75
- | input? | HTML element | An optional reference to control the calendar opening. The value is automatically bound when using this property. |
76
-
77
- ### Calendar Events
78
-
79
- | Event | Description |
80
- | -------------------------------- | ----------------------------------- |
81
- | onchange?: (self, value) => void | Called when a new date is selected. |
53
+ ## API Reference
54
+
55
+ ### Options
56
+
57
+ | Attribute | Type | Description |
58
+ |----------------|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
59
+ | `type?` | `'default' \| 'auto' \| 'picker' \| 'inline'` | Render mode. `'auto'` resolves to `'default'` (≥ 800px wide) or `'picker'` (smaller). `Default: 'default'`. |
60
+ | `format?` | `string` | Date format using Excel-like tokens (e.g. `'dd/mm/yyyy'`). |
61
+ | `value?` | `number \| string \| Date \| Array` | Initial value. ISO string (`'2025-06-15'`), Excel-style serial number, native `Date`, or `[start, end]` tuple in range mode. |
62
+ | `numeric?` | `boolean` | Return values as Excel-style numbers via `getValue()`. `Default: false`. |
63
+ | `range?` | `boolean` | Enable range selection. `Default: false`. |
64
+ | `footer?` | `boolean` | Show the footer (Reset / Done buttons + Today + time picker). `Default: true`. |
65
+ | `time?` | `boolean` | Show hour and minute selectors in the footer. `Default: false`. |
66
+ | `grid?` | `boolean` | Apply grid display mode (sets `data-grid="true"` on root). `Default: false`. |
67
+ | `placeholder?` | `string` | Placeholder applied to a bound input. |
68
+ | `disabled?` | `boolean` | Disable interaction. `Default: false`. |
69
+ | `startingDay?` | `number` | First day of the week (`0=Sun … 6=Sat`). `Default: 0`. |
70
+ | `validRange?` | `string[] \| number[] \| Function` | Bounds tuple `[from, to]` (ISO or Excel serial), or a predicate `(day, month, year, item) => boolean` to disable a cell. |
71
+ | `data?` | `Array<{ date: string, [key: string]: any }>` | Event entries; cells with a matching `date` get a `data-event` marker. |
72
+ | `wheel?` | `boolean` | Allow mouse-wheel month navigation. `Default: true`. |
73
+ | `input?` | `HTMLInputElement \| 'auto'` | Existing input to bind, or `'auto'` to create one. |
74
+ | `initInput?` | `boolean` | Wire up input listeners (open-on-focus, type-to-update). `Default: true`. |
75
+
76
+ ### Events
77
+
78
+ | Event | Signature | Description |
79
+ |-------------|------------------------------------------|-----------------------------------------------------------------------------------|
80
+ | `onchange?` | `(self, value) => void` | Fired when the value changes (date selection or `setValue`). |
81
+ | `onupdate?` | `(self, value) => void` | Fired when the cursor moves between cells (e.g. via arrow keys). |
82
+ | `onopen?` | `(self) => void` | Fired when the modal opens (`'picker'` / `'default'` types only). |
83
+ | `onclose?` | `(self, origin) => void` | Fired when the modal closes. `origin` is `'button'`, `'escape'`, `'focusout'`, or any custom string passed to `close({ origin })`. |
84
+ | `onChange?` | `(event) => void` | **React-only.** Forwarded to the bound input's native `change` DOM event. Distinct from `onchange`. |
85
+
86
+ ### Instance
87
+
88
+ Calling `Calendar(root, options)` returns an instance exposing:
89
+
90
+ | Property / Method | Description |
91
+ |--------------------|----------------------------------------------------------------------------------------------|
92
+ | `el` | Root DOM element. |
93
+ | `value` | Current value (assignable; mutation triggers a re-render). |
94
+ | `format` | Active date format mask. |
95
+ | `type` | Resolved type (post-`'auto'` resolution). |
96
+ | `view` | Current view: `'days' \| 'months' \| 'years'`. |
97
+ | `range` | Range mode flag. |
98
+ | `rangeValues` | `[start, end]` numeric tuple while a range is active, `null` otherwise. |
99
+ | `modal` | Modal instance (only for `'picker'` / `'default'` types). |
100
+ | `content` | Inner content element (focus target for keyboard navigation). |
101
+ | `input` | Bound input element (auto-created or user-provided). |
102
+ | `open()` | Open the modal. |
103
+ | `close(options?)` | Close the modal. Pass `{ origin: '...' }` to label the close source. |
104
+ | `isClosed()` | `true` / `false` for modal types; `undefined` for `'inline'`. |
105
+ | `setView(view)` | Switch view. Accepts `'days'`, `'months'`, `'years'`. |
106
+ | `next()` | Navigate forward (month / year / year-block depending on the current view). |
107
+ | `prev()` | Navigate backward. |
108
+ | `reset()` | Clear the value and close the modal when applicable. |
109
+ | `getValue()` | Return the current value as a string (or number when `numeric: true`). |
110
+ | `setValue(v)` | Set the value programmatically. Accepts ISO strings, Excel serial numbers, or `[start, end]` tuples in range mode. |
111
+ | `update()` | Commit the current cursor selection (used by Done button). |
82
112
 
83
113
  ## License
84
114
 
85
- The LemonadeJS [Reactive JavaScript Calendar](https://lemonadejs.com/docs/plugins/calendar) is released under the MIT.
115
+ Released under the MIT license.
86
116
 
87
117
  ## Other Tools
88
118
 
89
- - [jSuites Plugins - JavaScript Calendar](https://jsuites.net/docs/javascript-calendar)
90
- - [Jspreadsheet - JavaScript Spreadsheet](https://jspreadsheet.com/)
119
+ - [jSuites Plugins - JavaScript Calendar](https://jsuites.net/docs/javascript-calendar)
120
+ - [Jspreadsheet - JavaScript Spreadsheet](https://jspreadsheet.com/)
package/dist/index.d.ts CHANGED
@@ -1,103 +1,131 @@
1
- /**
2
- * Official Type definitions for LemonadeJS plugins
3
- * https://lemonadejs.com
4
- * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
- */
6
-
7
- declare function Calendar(el: HTMLElement, options?: Calendar.Options): Calendar.Instance;
8
-
9
- declare namespace Calendar {
10
- interface Options {
11
- /** Calendar type. Use picker for a responsive modal, auto to automatic detect screen size and open between default or picker. */
12
- type?: 'default' | 'auto' | 'picker' | 'inline';
13
- /** Date format. Excel like format dd/mm/yyyy */
14
- format?: string;
15
- /** Range picker */
16
- range?: boolean;
17
- /** Value */
18
- value?: number | string;
19
- /** Calendar value will be a excel-like number or a ISO string. Default false */
20
- numeric?: boolean;
21
- /** Show Footer. Default: true **/
22
- footer?: boolean;
23
- /** Show hour and minute picker **/
24
- time?: boolean;
25
- /** Show grid mode. Default: false */
26
- grid?: boolean;
27
- /** Placeholder */
28
- placeholder?: string;
29
- /** Disabled. Default false **/
30
- disabled?: boolean;
31
- /** Starting day. From 0-6 where zero is Sunday and six is Saturday */
32
- startingDay?: number;
33
- /** Valid range **/
34
- validRange?: number[] | string[];
35
- /** Calendar events data */
36
- data?: Array<{date: string, [key: string]: any}>;
37
- /** Update view on mouse wheel. Default: true */
38
- wheel?: boolean;
39
- /** Bind the calendar to na HTML input element */
40
- input?: HTMLInputElement | 'auto';
41
- /** Create events and assing the calendar classes for the input. Default true */
42
- initInput?: boolean;
43
- /** Change value event */
44
- onchange?: (self: object, value: string) => void;
45
- /** Update view event */
46
- onupdate?: (self: object, value: string) => void;
47
- /** Close event */
48
- onclose?: (self: object, origin: string) => void;
49
- /** Open event */
50
- onopen?: (self: object) => void;
51
- /** React dedicated onChange event */
52
- onChange?: (e: Event) => void;
53
- }
54
-
55
- interface Instance {
56
- /** Calendar type */
57
- type?: 'default' | 'auto' | 'picker' | 'inline';
58
- /** Date format */
59
- format: string;
60
- /** Range picker */
61
- range: boolean;
62
- /** Value */
63
- value: number | string;
64
- /** Calendar value will be a excel-like number or a ISO string. Default false */
65
- numeric: boolean;
66
- /** Footer. Default: true **/
67
- footer: boolean;
68
- /** Show hour and minute picker **/
69
- time: boolean;
70
- /** Show grid mode. Default: false */
71
- grid: boolean;
72
- /** Placeholder */
73
- placeholder: string;
74
- /** Disabled. Default false **/
75
- disabled: boolean;
76
- /** Update view on mouse wheel. Default: true */
77
- wheel: boolean;
78
- /** Bind the calendar to na HTML input element */
79
- input: HTMLElement;
80
- /** Open the calendar modal */
81
- open: () => void;
82
- /** Open the calendar modal */
83
- close: (options?: object) => void;
84
- /** Calendar is closed */
85
- isClosed?: () => boolean;
86
- /** Change the view */
87
- setView: (view: 'days' | 'months' | 'years') => void;
88
- /** Go to the next month or year depending on the view */
89
- next?: () => void;
90
- /** Go to the previous month or year depending on the view */
91
- prev?: () => void;
92
- /** Reset the calendar value and close the modal when applicable */
93
- reset?: () => void;
94
- /** Get the current calendar value */
95
- getValue?: () => string | number;
96
- /** Set the current calendar value. Use number for Excel like numbers */
97
- setValue?: (value: string | number) => void;
98
- /** Accept the selected value on the calendar */
99
- update?: () => void;
100
- }
101
- }
102
-
103
- export default Calendar;
1
+ /**
2
+ * Official Type definitions for LemonadeJS plugins
3
+ * https://lemonadejs.com
4
+ * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
+ */
6
+
7
+ declare function Calendar(el: HTMLElement, options?: Calendar.Options): Calendar.Instance;
8
+
9
+ declare namespace Calendar {
10
+ /** Item passed to a `validRange` function — describes the cell being evaluated. */
11
+ interface ValidRangeItem {
12
+ date: string;
13
+ [key: string]: any;
14
+ }
15
+
16
+ /** `validRange` may be a tuple of bounds (numeric or ISO strings) or a predicate
17
+ * returning truthy to disable a cell. */
18
+ type ValidRange =
19
+ | number[]
20
+ | string[]
21
+ | ((day: number, month: number, year: number, item: ValidRangeItem) => boolean | void);
22
+
23
+ interface CloseOptions {
24
+ /** Where the close was triggered from: 'button', 'escape', 'focusout', or any custom string. */
25
+ origin?: string;
26
+ }
27
+
28
+ interface Options {
29
+ /** Calendar type. Use picker for a responsive modal, auto to automatic detect screen size and open between default or picker. */
30
+ type?: 'default' | 'auto' | 'picker' | 'inline';
31
+ /** Date format. Excel like format dd/mm/yyyy */
32
+ format?: string;
33
+ /** Range picker */
34
+ range?: boolean;
35
+ /** Initial value. Accepts an ISO string, an Excel serial number, a `Date`, or `[start, end]` for range mode. */
36
+ value?: number | string | Date | Array<number | string | Date>;
37
+ /** Calendar value will be a excel-like number or a ISO string. Default false */
38
+ numeric?: boolean;
39
+ /** Show Footer. Default: true **/
40
+ footer?: boolean;
41
+ /** Show hour and minute picker **/
42
+ time?: boolean;
43
+ /** Show grid mode. Default: false */
44
+ grid?: boolean;
45
+ /** Placeholder */
46
+ placeholder?: string;
47
+ /** Disabled. Default false **/
48
+ disabled?: boolean;
49
+ /** Starting day of the week. From 0-6 where zero is Sunday and six is Saturday */
50
+ startingDay?: number;
51
+ /** Bounds tuple, or predicate returning truthy to disable a cell. */
52
+ validRange?: ValidRange;
53
+ /** Calendar events data */
54
+ data?: Array<{ date: string; [key: string]: any }>;
55
+ /** Update view on mouse wheel. Default: true */
56
+ wheel?: boolean;
57
+ /** Bind the calendar to an HTML input element, or `'auto'` to create one. */
58
+ input?: HTMLInputElement | 'auto';
59
+ /** Create events and assign the calendar classes for the input. Default: true */
60
+ initInput?: boolean;
61
+ /** Fired when the calendar value changes (date selection). */
62
+ onchange?: (self: Instance, value: string | number) => void;
63
+ /** Fired when the cursor moves between cells (e.g. arrow-key navigation). */
64
+ onupdate?: (self: Instance, value: string) => void;
65
+ /** Fired when the modal closes. `origin` identifies the source ('button', 'escape', 'focusout', or custom). */
66
+ onclose?: (self: Instance, origin: string) => void;
67
+ /** Fired when the modal opens. */
68
+ onopen?: (self: Instance) => void;
69
+ /** React-only: forwarded to the bound input's native `change` DOM event. Distinct from `onchange`. */
70
+ onChange?: (e: Event) => void;
71
+ }
72
+
73
+ interface Instance {
74
+ /** The root DOM element. */
75
+ el: HTMLElement;
76
+ /** Calendar type may be re-assigned at runtime when initial type is 'auto'. */
77
+ type?: 'default' | 'auto' | 'picker' | 'inline';
78
+ /** Date format */
79
+ format: string;
80
+ /** Range picker */
81
+ range: boolean;
82
+ /** Value */
83
+ value: number | string;
84
+ /** Calendar value will be a excel-like number or a ISO string. Default false */
85
+ numeric: boolean;
86
+ /** Footer. Default: true **/
87
+ footer: boolean;
88
+ /** Show hour and minute picker **/
89
+ time: boolean;
90
+ /** Show grid mode. Default: false */
91
+ grid: boolean;
92
+ /** Placeholder */
93
+ placeholder: string;
94
+ /** Disabled. Default false **/
95
+ disabled: boolean;
96
+ /** Update view on mouse wheel. Default: true */
97
+ wheel: boolean;
98
+ /** Bound input element (created when `input: 'auto'`). */
99
+ input: HTMLInputElement;
100
+ /** Modal instance — present only when `type` resolves to 'picker' or 'default'. */
101
+ modal?: any;
102
+ /** Inner content element used as the focusable target for keyboard navigation. */
103
+ content?: HTMLElement;
104
+ /** Current view. */
105
+ view?: 'days' | 'months' | 'years';
106
+ /** Active range tuple as numeric (Excel-like) values when `range: true`; null otherwise. */
107
+ rangeValues: [number, number] | null;
108
+ /** Open the calendar modal */
109
+ open: () => void;
110
+ /** Close the calendar modal */
111
+ close: (options?: CloseOptions) => void;
112
+ /** Whether the calendar modal is currently closed. Returns undefined for inline calendars. */
113
+ isClosed?: () => boolean | undefined;
114
+ /** Change the view */
115
+ setView: (view: 'days' | 'months' | 'years') => void;
116
+ /** Go to the next month or year depending on the view */
117
+ next?: () => void;
118
+ /** Go to the previous month or year depending on the view */
119
+ prev?: () => void;
120
+ /** Reset the calendar value and close the modal when applicable */
121
+ reset?: () => void;
122
+ /** Get the current calendar value */
123
+ getValue?: () => string | number;
124
+ /** Set the current calendar value. Accepts ISO strings, Excel serial numbers, `Date` objects, or `[start, end]` tuples in range mode. */
125
+ setValue?: (value: string | number | Date | Array<number | string | Date>) => void;
126
+ /** Accept the selected value on the calendar */
127
+ update?: () => void;
128
+ }
129
+ }
130
+
131
+ export default Calendar;
package/dist/index.js CHANGED
@@ -63,6 +63,9 @@ const Mask = utils.Mask;
63
63
  let data = {};
64
64
  if (Array.isArray(this.data)) {
65
65
  this.data.map(function (v) {
66
+ if (!v || typeof v !== 'object' || typeof v.date !== 'string') {
67
+ return;
68
+ }
66
69
  let d = year + '-' + Helpers.two(month + 1);
67
70
  if (v.date.substring(0, 7) === d) {
68
71
  if (!data[v.date]) {
@@ -291,8 +294,13 @@ const Mask = utils.Mask;
291
294
  let change = self.onchange;
292
295
  self.onchange = null;
293
296
 
297
+ // Coerce startingDay to a number so string inputs ('1') don't trigger string concat in modulo math
298
+ if (typeof self.startingDay !== 'number') {
299
+ self.startingDay = Number(self.startingDay) || 0;
300
+ }
301
+
294
302
  // Weekdays
295
- self.weekdays = getWeekdays(self.startingDay ?? 0);
303
+ self.weekdays = getWeekdays(self.startingDay);
296
304
 
297
305
  // Cursor
298
306
  self.cursor = {};
@@ -380,6 +388,10 @@ const Mask = utils.Mask;
380
388
  const setValue = function(v) {
381
389
  let d = new Date();
382
390
  if (v) {
391
+ // Accept native Date objects by converting to ISO string
392
+ if (v instanceof Date) {
393
+ v = v.toISOString().substring(0, 10);
394
+ }
383
395
  if (isTrue(self.range)) {
384
396
  if (v) {
385
397
  if (! Array.isArray(v)) {
@@ -508,6 +520,9 @@ const Mask = utils.Mask;
508
520
  }
509
521
 
510
522
  const select = function(e, s) {
523
+ if (self.disabled === true) {
524
+ return;
525
+ }
511
526
  // Get new date content
512
527
  let d = updateDate(s.value, getPosition());
513
528
  // New date
@@ -536,7 +551,7 @@ const Mask = utils.Mask;
536
551
  } else {
537
552
  setCursor(s);
538
553
 
539
- update();
554
+ update(e);
540
555
  }
541
556
  }
542
557
  }
@@ -544,7 +559,10 @@ const Mask = utils.Mask;
544
559
  // Update Calendar
545
560
  const update = function(e) {
546
561
  self.setValue(getValue());
547
- self.close({ origin: 'button' });
562
+
563
+ if (! (e && e.type === 'click' && e.target.tagName === 'DIV' && self.time === true)) {
564
+ self.close({ origin: 'button' });
565
+ }
548
566
  }
549
567
 
550
568
  const reset = function() {
@@ -696,11 +714,17 @@ const Mask = utils.Mask;
696
714
  }
697
715
 
698
716
  const normalize = function(v) {
717
+ if (v instanceof Date) {
718
+ v = Helpers.dateToString ? Helpers.dateToString(v) : v.toISOString().substring(0, 10);
719
+ }
699
720
  if (! Array.isArray(v)) {
700
721
  v = v.toString().split(',');
701
722
  }
702
723
 
703
724
  return v.map(item => {
725
+ if (item instanceof Date) {
726
+ return Helpers.dateToString ? Helpers.dateToString(item) : item.toISOString().substring(0, 10);
727
+ }
704
728
  if (Number(item) == item) {
705
729
  return Helpers.numToDate(item);
706
730
  } else {
@@ -824,7 +848,7 @@ const Mask = utils.Mask;
824
848
  }
825
849
  } else if (e.code === 'Enter') {
826
850
  if (! self.isClosed()) {
827
- update();
851
+ update(e);
828
852
  } else {
829
853
  self.open();
830
854
  }
@@ -1009,7 +1033,10 @@ const Mask = utils.Mask;
1009
1033
  if (prop === 'view') {
1010
1034
  reloadView(true);
1011
1035
  } else if (prop === 'startingDay') {
1012
- self.weekdays = getWeekdays(self.startingDay ?? 0);
1036
+ if (typeof self.startingDay !== 'number') {
1037
+ self.startingDay = Number(self.startingDay) || 0;
1038
+ }
1039
+ self.weekdays = getWeekdays(self.startingDay);
1013
1040
  } else if (prop === 'value') {
1014
1041
  dispatchOnChangeEvent();
1015
1042
  }
package/dist/react.d.ts CHANGED
@@ -1,18 +1,55 @@
1
- /**
2
- * Official Type definitions for the LemonadeJS plugins
3
- * https://lemonadejs.net/docs/plugins/calendar
4
- * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
- */
6
- import Component from './index';
7
-
8
- interface Calendar {
9
- ref?: MutableRefObject<undefined>;
10
- (): any
11
- [key: string]: any
12
- }
13
-
14
- type Props = IntrinsicAttributes & Component.Options & Calendar;
15
-
16
- declare function Calendar<Calendar>(props: Props): any;
17
-
18
- export default Calendar;
1
+ /**
2
+ * Official Type definitions for the LemonadeJS plugins
3
+ * https://lemonadejs.net/docs/plugins/calendar
4
+ * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
+ */
6
+ import React from 'react';
7
+ import Calendar from './index';
8
+
9
+ declare namespace CalendarReact {
10
+ /**
11
+ * React-idiomatic camelCase aliases for the lemonadejs callbacks. The
12
+ * wrapper drops the leading `self` argument before invoking these — the
13
+ * instance is available via the forwarded ref.
14
+ *
15
+ * The lowercase names from `Calendar.Options` (e.g. `onchange`) still
16
+ * work for legacy compatibility, but new code should prefer these.
17
+ */
18
+ interface ReactCallbacks {
19
+ /** Fired when the value changes (date selection or `setValue`). */
20
+ onChange?: (value: string | number) => void;
21
+ /** Fired when the cursor moves between cells (e.g. via arrow keys). */
22
+ onUpdate?: (value: string) => void;
23
+ /** Fired when the modal opens. */
24
+ onOpen?: () => void;
25
+ /**
26
+ * Fired when the modal closes. `origin` is `'button'`, `'escape'`,
27
+ * `'focusout'`, or any custom string passed to `close({ origin })`.
28
+ */
29
+ onClose?: (origin: string) => void;
30
+ /**
31
+ * Wired to the bound input element's native DOM `change` event.
32
+ * Renamed from the legacy `onChange` to free that name for
33
+ * `onChange` (the date-selection callback above).
34
+ */
35
+ onInputChange?: (e: Event) => void;
36
+ }
37
+
38
+ /** Wrapper-only props (the wrapping `<div>` accepts these). */
39
+ interface DOMProps {
40
+ className?: string;
41
+ style?: React.CSSProperties;
42
+ children?: React.ReactNode;
43
+ }
44
+ }
45
+
46
+ declare const CalendarReact: React.MemoExoticComponent<
47
+ React.ForwardRefExoticComponent<
48
+ Omit<Calendar.Options, 'onchange' | 'onupdate' | 'onclose' | 'onopen' | 'onChange'>
49
+ & CalendarReact.ReactCallbacks
50
+ & CalendarReact.DOMProps
51
+ & React.RefAttributes<Calendar.Instance>
52
+ >
53
+ >;
54
+
55
+ export default CalendarReact;
package/dist/react.js CHANGED
@@ -1,42 +1,112 @@
1
- // @ts-nocheck
2
- import React, { useRef, useEffect } from "react";
3
- import Component from './index';
4
-
5
- // @ts-ignore
6
- export default React.forwardRef((props, mainReference) => {
7
- // Dom element
8
- const Ref = useRef(null);
9
-
10
- // Get the properties for the spreadsheet
11
- let options = { ...props };
12
-
13
- useEffect(() => {
14
- // @ts-ignore
15
- if (Ref.current.innerHTML) {
16
- for (let key in props) {
17
- if (key === 'onchange' || key === 'onload') {
18
- continue;
19
- }
20
- if (props.hasOwnProperty(key) && mainReference.current.hasOwnProperty(key)) {
21
- if (props[key] !== mainReference.current[key]) {
22
- mainReference.current[key] = props[key];
23
- }
24
- }
25
- }
26
- }
27
- }, [props])
28
-
29
- useEffect(() => {
30
- // @ts-ignore
31
- if (! Ref.current.innerHTML) {
32
- mainReference.current = Component(Ref.current, options);
33
- }
34
- }, []);
35
-
36
- let prop = {
37
- ref: Ref,
38
- style: { height: '100%', width: '100%' }
39
- };
40
-
41
- return React.createElement("div", prop);
42
- })
1
+ // @ts-nocheck
2
+ import React, {
3
+ useRef,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useState,
7
+ memo,
8
+ } from 'react';
9
+ import Component from './index';
10
+
11
+ // Maps React-style camelCase prop names to the underlying lemonadejs
12
+ // lowercase option names. The wrapper drops the leading `self` argument
13
+ // from camelCase callbacks before invoking the user's handler — `self` is
14
+ // redundant in React because the user already has access to the instance
15
+ // through the forwarded ref.
16
+ //
17
+ // `onInputChange` is calendar-specific: the lemonadejs plugin uses `onChange`
18
+ // (capital C) as a hook for the bound input element's native DOM `change`
19
+ // event. Renaming it to `onInputChange` frees `onChange` to mean "date
20
+ // selection callback" — matching what React users expect.
21
+ const REACT_TO_LEMONADE = {
22
+ onChange: 'onchange',
23
+ onUpdate: 'onupdate',
24
+ onCreate: 'oncreate',
25
+ onBeforeCreate: 'onbeforecreate',
26
+ onChangeEvent: 'onchangeevent',
27
+ onBeforeChangeEvent: 'onbeforechangeevent',
28
+ onBeforeChange: 'onbeforechange',
29
+ onBeforeInsert: 'onbeforeinsert',
30
+ onDelete: 'ondelete',
31
+ onError: 'onerror',
32
+ onClose: 'onclose',
33
+ onOpen: 'onopen',
34
+ onEdition: 'onedition',
35
+ onDblClick: 'ondblclick',
36
+ onInputChange: 'onChange',
37
+ };
38
+
39
+ const RESERVED_REACT_PROPS = new Set(['children', 'className', 'style', 'key']);
40
+
41
+ const Calendar = memo(React.forwardRef((props, mainReference) => {
42
+ const containerRef = useRef(null);
43
+ const instanceRef = useRef(null);
44
+ const propsRef = useRef(props);
45
+ const [instance, setInstance] = useState(null);
46
+
47
+ // Keep a ref to the latest props so callback trampolines always invoke
48
+ // the user's most recent function (avoids stale-closure bugs on parent
49
+ // rerenders).
50
+ propsRef.current = props;
51
+
52
+ // Single-mount effect, StrictMode-safe via the `instance` guard.
53
+ useEffect(() => {
54
+ if (!containerRef.current || instance) return;
55
+
56
+ const options = {};
57
+ for (const key in props) {
58
+ if (RESERVED_REACT_PROPS.has(key)) continue;
59
+ const lemonadeKey = REACT_TO_LEMONADE[key] || key;
60
+ const isReactCallback = key in REACT_TO_LEMONADE;
61
+ const value = props[key];
62
+ if (typeof value === 'function') {
63
+ options[lemonadeKey] = isReactCallback
64
+ // React-style: drop the leading `self` argument
65
+ ? (...args) => {
66
+ const fn = propsRef.current[key];
67
+ if (typeof fn === 'function') return fn(...args.slice(1));
68
+ }
69
+ // Legacy lowercase callback: pass through unchanged
70
+ : (...args) => {
71
+ const fn = propsRef.current[key];
72
+ if (typeof fn === 'function') return fn(...args);
73
+ };
74
+ } else {
75
+ options[lemonadeKey] = value;
76
+ }
77
+ }
78
+
79
+ const newInstance = Component(containerRef.current, options);
80
+ instanceRef.current = newInstance;
81
+ setInstance(newInstance);
82
+ }, []);
83
+
84
+ // Prop sync: write non-function prop changes back to the live instance.
85
+ useEffect(() => {
86
+ const inst = instanceRef.current;
87
+ if (!inst) return;
88
+ for (const key in props) {
89
+ if (RESERVED_REACT_PROPS.has(key)) continue;
90
+ if (typeof props[key] === 'function') continue;
91
+ const lemonadeKey = REACT_TO_LEMONADE[key] || key;
92
+ if (Object.prototype.hasOwnProperty.call(inst, lemonadeKey)
93
+ && props[key] !== inst[lemonadeKey]) {
94
+ inst[lemonadeKey] = props[key];
95
+ }
96
+ }
97
+ }, [props]);
98
+
99
+ // Expose the lemonadejs instance through the forwarded ref.
100
+ useImperativeHandle(mainReference, () => instance, [instance]);
101
+
102
+ return React.createElement(React.Fragment, null,
103
+ React.createElement('div', {
104
+ ref: containerRef,
105
+ className: props.className,
106
+ style: { width: '100%', height: '100%', ...props.style },
107
+ }),
108
+ props.children,
109
+ );
110
+ }));
111
+
112
+ export default Calendar;
package/dist/vue.d.ts CHANGED
@@ -1,15 +1,22 @@
1
- /**
2
- * Official Type definitions for the LemonadeJS plugins
3
- * https://lemonadejs.net
4
- * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
- */
6
- import Component from './index';
7
-
8
- interface Calendar {
9
- (): any
10
- [key: string]: any
11
- }
12
-
13
- declare function Calendar<Calendar>(props: Component.Options): any;
14
-
15
- export default Calendar;
1
+ /**
2
+ * Official Type definitions for the LemonadeJS plugins
3
+ * https://lemonadejs.net
4
+ * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
+ */
6
+ import { DefineComponent } from 'vue';
7
+ import Calendar from './index';
8
+
9
+ /**
10
+ * Vue 3 wrapper. Use `<Calendar ... ref="calendarRef" />` and access the
11
+ * underlying instance via `calendarRef.value?.current` (e.g. `current.setValue(...)`).
12
+ */
13
+ declare const CalendarVue: DefineComponent<
14
+ Calendar.Options,
15
+ {},
16
+ {
17
+ el: HTMLElement | null;
18
+ current: Calendar.Instance | null;
19
+ }
20
+ >;
21
+
22
+ export default CalendarVue;
package/dist/vue.js CHANGED
@@ -1,47 +1,39 @@
1
- import { h } from 'vue';
2
- import component from "./index";
3
-
4
-
5
- export default {
6
- inheritAttrs: false,
7
- mounted() {
8
- this.$nextTick(() => {
9
- let options = {
10
- ...this.$attrs
11
- };
12
-
13
- this.current = component(this.$refs.container, options);
14
- });
15
- },
16
- setup() {
17
- let containerProps = {
18
- ref: 'container',
19
- style: {
20
- width: '100%',
21
- height: '100%',
22
- }
23
- };
24
- return () => h('div', containerProps);
25
- },
26
- watch: {
27
- $attrs: {
28
- deep: true,
29
- handler() {
30
- this.updateState();
31
- }
32
- }
33
- },
34
- methods: {
35
- updateState() {
36
- if (this.current) {
37
- for (let key in this.$attrs) {
38
- if (this.$attrs.hasOwnProperty(key) && this.current.hasOwnProperty(key)) {
39
- if (this.$attrs[key] !== this.current[key]) {
40
- this.current[key] = this.$attrs[key];
41
- }
42
- }
43
- }
44
- }
45
- }
46
- }
47
- }
1
+ import { h } from 'vue';
2
+ import component from './index';
3
+
4
+ export default {
5
+ inheritAttrs: false,
6
+ mounted() {
7
+ this.$nextTick(() => {
8
+ const options = { ...this.$attrs };
9
+ this.el = this.$refs.container;
10
+ this.current = component(this.$refs.container, options);
11
+ });
12
+ },
13
+ setup() {
14
+ return () => h('div', {
15
+ ref: 'container',
16
+ style: { width: '100%', height: '100%' },
17
+ });
18
+ },
19
+ watch: {
20
+ $attrs: {
21
+ deep: true,
22
+ handler() {
23
+ this.updateState();
24
+ },
25
+ },
26
+ },
27
+ methods: {
28
+ updateState() {
29
+ if (!this.current) return;
30
+ for (const key in this.$attrs) {
31
+ if (Object.prototype.hasOwnProperty.call(this.$attrs, key)
32
+ && Object.prototype.hasOwnProperty.call(this.current, key)
33
+ && this.$attrs[key] !== this.current[key]) {
34
+ this.current[key] = this.$attrs[key];
35
+ }
36
+ }
37
+ },
38
+ },
39
+ };
package/package.json CHANGED
@@ -20,5 +20,21 @@
20
20
  },
21
21
  "main": "dist/index.js",
22
22
  "types": "dist/index.d.ts",
23
- "version": "5.8.3"
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./react": {
29
+ "types": "./dist/react.d.ts",
30
+ "default": "./dist/react.js"
31
+ },
32
+ "./vue": {
33
+ "types": "./dist/vue.d.ts",
34
+ "default": "./dist/vue.js"
35
+ },
36
+ "./style.css": "./dist/style.css",
37
+ "./dist/*": "./dist/*"
38
+ },
39
+ "version": "5.9.0"
24
40
  }