ten-protons-datepicker 1.0.1 → 1.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.
package/README.md CHANGED
@@ -22,9 +22,11 @@ npm i ten-protons-datepicker
22
22
  import 'ten-protons-datepicker/locales/uk'; // optional UI strings for locale="uk"
23
23
  </script>
24
24
 
25
- <x-datepicker name="period" mode="range" locale="uk" min="2025-01-01" label="Period"></x-datepicker>
25
+ <ten-protons-datepicker name="period" mode="range" locale="uk" min="2025-01-01" label="Period"></ten-protons-datepicker>
26
26
  ```
27
27
 
28
+ `<x-datepicker>` is registered as an alias for the same element, so either tag works. Prefer `ten-protons-datepicker`: `x-datepicker` is a generic name another library could also claim, and the first one registered wins.
29
+
28
30
  `mode="range"` / `mode="multiple"` and `enable-time` load their module on demand, so single-date pages don't pay for them.
29
31
 
30
32
  | Attribute | Notes |
@@ -35,14 +37,37 @@ npm i ten-protons-datepicker
35
37
  | `number-of-months`, `show-other-months`, `allow-input`, `close-on-select`, `disabled-days`, `output-timestamp`, `enable-time`, `value-in-utc`, `inline` | booleans: present = true, `="false"` = false |
36
38
 
37
39
  ```js
38
- const el = document.querySelector('x-datepicker');
40
+ const el = document.querySelector('ten-protons-datepicker');
39
41
  el.value; // ISO string of the current selection
40
42
  el.options = { disabledDates: (d) => d.getDay() === 0, onChange: console.log };
41
43
  el.addEventListener('change', (e) => console.log(e.detail)); // also: open, close, monthChange, yearChange
42
44
  await el.ready; // resolves with the underlying Datepicker
43
45
  ```
44
46
 
45
- Style with `::part(input)`, `::part(toggle)` and the `--tp-*` variables.
47
+ The input is styled out of the box to match the calendar (border, radius and colours, light and dark, red border on invalid input). Restyle it with `::part(input)` and `::part(toggle)`, e.g. `ten-protons-datepicker::part(input) { border-radius: 0 }`. The calendar button is the web component's own icon (at the start of the field, like `showIcon` on a plain input), so `showIcon` doesn't apply to it.
48
+
49
+ ### Vue
50
+
51
+ Tell Vue that the tag is a custom element (Vite: `vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === 'ten-protons-datepicker' } } })`), then bind `value` and listen to `change`:
52
+
53
+ ```vue
54
+ <script setup lang="ts">
55
+ import 'ten-protons-datepicker/wc'; // its CSS is built in; enable-time loads the time module itself
56
+
57
+ defineProps<{ modelValue: number | null }>();
58
+ const emit = defineEmits<{ 'update:modelValue': [timestamp: number | null] }>();
59
+ const onChange = (e: Event) => {
60
+ const value = (e as CustomEvent).detail;
61
+ emit('update:modelValue', typeof value === 'number' ? value : null);
62
+ };
63
+ </script>
64
+
65
+ <template>
66
+ <ten-protons-datepicker :value="modelValue ?? ''" label="Due" value-in-utc output-timestamp enable-time @change="onChange" />
67
+ </template>
68
+ ```
69
+
70
+ Attributes such as `theme` and `max` can be bound the same way. The element is created and destroyed with the component, so there is nothing to clean up. Other frameworks work the same: set attributes, listen to `change`.
46
71
 
47
72
  ## Imperative API
48
73
 
@@ -149,7 +174,7 @@ const dp = withTime(new Datepicker(input, { dateFormat: 'dd.MM.yyyy', onChange:
149
174
  ```
150
175
 
151
176
  ```html
152
- <x-datepicker enable-time output-timestamp name="meeting"></x-datepicker> <!-- loads the time module itself -->
177
+ <ten-protons-datepicker enable-time output-timestamp name="meeting"></ten-protons-datepicker> <!-- loads the time module itself -->
153
178
  ```
154
179
 
155
180
  How it behaves:
@@ -198,7 +223,7 @@ withTime(new Datepicker(input, { dateFormat: 'dd.MM.yyyy', outputTimestamp: true
198
223
  // typed "1742044029" shows 15.03.2025 13:07:09 getDate() = 1742044029 exact round trip
199
224
  ```
200
225
 
201
- Ranges work the same way: `withTime(withRange(new Datepicker(input, { outputTimestamp: true })))` returns `[1741597200, 1741804200]` for 10 March 09:00:00 to 12 March 18:30:00 (UTC). In the web component, `<x-datepicker enable-time mode="range">` has the value `2025-03-10T09:00:00/2025-03-12T18:30:00`, or `1741597200/1741804200` with `output-timestamp`.
226
+ Ranges work the same way: `withTime(withRange(new Datepicker(input, { outputTimestamp: true })))` returns `[1741597200, 1741804200]` for 10 March 09:00:00 to 12 March 18:30:00 (UTC). In the web component, `<ten-protons-datepicker enable-time mode="range">` has the value `2025-03-10T09:00:00/2025-03-12T18:30:00`, or `1741597200/1741804200` with `output-timestamp`.
202
227
 
203
228
  Things to know:
204
229
 
@@ -217,7 +242,7 @@ dp.getDate().toISOString(); // '2025-03-15T00:00:00.000Z', s
217
242
  ```
218
243
 
219
244
  ```html
220
- <x-datepicker name="due" value-in-utc value="2025-03-15T00:00:00.000Z"></x-datepicker>
245
+ <ten-protons-datepicker name="due" value-in-utc value="2025-03-15T00:00:00.000Z"></ten-protons-datepicker>
221
246
  ```
222
247
 
223
248
  Without it, a user in Kyiv (UTC+2) who picks 15 March gets a `Date` for **local** midnight, and `toISOString()` sends `2025-03-14T22:00:00.000Z`: the backend stores the 14th. That is the bug this option removes.
package/dist/core.css CHANGED
@@ -1,8 +1,10 @@
1
- .tp-dp{
1
+ .tp-dp,:host{
2
2
  --tp-bg:#fff;--tp-fg:#1f2328;--tp-muted:#656d76;--tp-border:#d0d7de;--tp-hover:#eef1f4;
3
3
  --tp-accent:#2563eb;--tp-accent-fg:#fff;--tp-range:#dbe7fd;--tp-focus:#2563eb;
4
4
  --tp-radius:.5rem;--tp-cell:2.25rem;--tp-pad:.75rem;--tp-z:1000;
5
- --tp-shadow:0 8px 24px rgba(0,0,0,.15);
5
+ --tp-shadow:0 8px 24px rgba(0,0,0,.15)
6
+ }
7
+ .tp-dp{
6
8
  position:relative;box-sizing:border-box;width:max-content;max-width:calc(100vw - 1rem);padding:var(--tp-pad);
7
9
  background:var(--tp-bg);color:var(--tp-fg);border:1px solid var(--tp-border);border-radius:var(--tp-radius);
8
10
  font:.875rem/1.4 var(--tp-font,inherit)
package/dist/themes.css CHANGED
@@ -1,10 +1,10 @@
1
1
  /* Dark follows prefers-color-scheme unless data-theme="light"; data-theme="dark" forces it. */
2
- .tp-dp[data-theme=dark]{
2
+ .tp-dp[data-theme=dark],:host([theme=dark]){
3
3
  --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
4
4
  --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
5
5
  }
6
6
  @media (prefers-color-scheme:dark){
7
- .tp-dp:not([data-theme=light]){
7
+ .tp-dp:not([data-theme=light]),:host(:not([theme=light])){
8
8
  --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
9
9
  --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
10
10
  }
package/dist/wc.d.ts CHANGED
@@ -2,7 +2,7 @@ import { a as Datepicker, d as DatepickerOptions } from './Datepicker-Dt5Dt_UX.j
2
2
 
3
3
  declare const Base: typeof HTMLElement;
4
4
  /**
5
- * `<x-datepicker>`: an input + calendar toggle with the popup inside its shadow root.
5
+ * `<ten-protons-datepicker>` (alias `<x-datepicker>`): an input + calendar toggle with the popup inside its shadow root.
6
6
  *
7
7
  * Attributes: `value` (ISO or Unix seconds, ranges `a/b`, multiple `a,b`), `name`, `placeholder`, `label`, `disabled`,
8
8
  * `mode`, `locale`, `min`, `max`, `date-format`, `first-day`, `position`, `theme`, `number-of-months`,
@@ -51,6 +51,7 @@ declare class XDatepicker extends Base {
51
51
  declare global {
52
52
  interface HTMLElementTagNameMap {
53
53
  'x-datepicker': XDatepicker;
54
+ 'ten-protons-datepicker': XDatepicker;
54
55
  }
55
56
  }
56
57
 
package/dist/wc.js CHANGED
@@ -1,8 +1,10 @@
1
- import {a as a$1}from'./chunk-MTBE7EMZ.js';import'./chunk-CUGUCENC.js';import'./chunk-Q2P4Y4T6.js';import'./chunk-BHROFXOE.js';import'./chunk-ANB32WKT.js';var d=`.tp-dp{
1
+ import {a as a$1}from'./chunk-MTBE7EMZ.js';import'./chunk-CUGUCENC.js';import'./chunk-Q2P4Y4T6.js';import'./chunk-BHROFXOE.js';import'./chunk-ANB32WKT.js';var l=`.tp-dp,:host{
2
2
  --tp-bg:#fff;--tp-fg:#1f2328;--tp-muted:#656d76;--tp-border:#d0d7de;--tp-hover:#eef1f4;
3
3
  --tp-accent:#2563eb;--tp-accent-fg:#fff;--tp-range:#dbe7fd;--tp-focus:#2563eb;
4
4
  --tp-radius:.5rem;--tp-cell:2.25rem;--tp-pad:.75rem;--tp-z:1000;
5
- --tp-shadow:0 8px 24px rgba(0,0,0,.15);
5
+ --tp-shadow:0 8px 24px rgba(0,0,0,.15)
6
+ }
7
+ .tp-dp{
6
8
  position:relative;box-sizing:border-box;width:max-content;max-width:calc(100vw - 1rem);padding:var(--tp-pad);
7
9
  background:var(--tp-bg);color:var(--tp-fg);border:1px solid var(--tp-border);border-radius:var(--tp-radius);
8
10
  font:.875rem/1.4 var(--tp-font,inherit)
@@ -60,15 +62,15 @@ import {a as a$1}from'./chunk-MTBE7EMZ.js';import'./chunk-CUGUCENC.js';import'./
60
62
  @media (forced-colors:active){
61
63
  .tp-selected{forced-color-adjust:none;background:Highlight;color:HighlightText}
62
64
  }
63
- `;var l=`/* Dark follows prefers-color-scheme unless data-theme="light"; data-theme="dark" forces it. */
64
- .tp-dp[data-theme=dark]{
65
+ `;var c=`/* Dark follows prefers-color-scheme unless data-theme="light"; data-theme="dark" forces it. */
66
+ .tp-dp[data-theme=dark],:host([theme=dark]){
65
67
  --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
66
68
  --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
67
69
  }
68
70
  @media (prefers-color-scheme:dark){
69
- .tp-dp:not([data-theme=light]){
71
+ .tp-dp:not([data-theme=light]),:host(:not([theme=light])){
70
72
  --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
71
73
  --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
72
74
  }
73
75
  }
74
- `;var f={range:()=>import('./range.js'),multiple:()=>import('./multiple.js')},v=()=>import('./time.js'),o=e=>e??void 0,c=e=>e===null?void 0:Number(e),a=e=>e===null?void 0:e!=="false",k=e=>e===null?void 0:e.split(",").map(Number),u=e=>e.split(/\s*[/,;]\s*/).filter(Boolean),h={mode:["mode",o],locale:["locale",o],min:["minDate",o],max:["maxDate",o],"date-format":["dateFormat",o],"first-day":["firstDayOfWeek",c],position:["position",o],theme:["theme",o],"number-of-months":["numberOfMonths",c],"show-other-months":["showOtherMonths",a],"allow-input":["allowInput",a],"close-on-select":["closeOnSelect",a],"disabled-days":["disabledDaysOfWeek",k],"output-timestamp":["outputTimestamp",a],"enable-time":["enableTime",a],"value-in-utc":["valueInUtc",a],inline:["inline",a]},w=["change","open","close","monthChange","yearChange"],y='<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></svg>',x=":host{display:inline-block}:host([hidden]){display:none}.wrap{position:relative;display:flex;align-items:center}input{box-sizing:border-box;width:100%;padding-inline-end:2.25rem;font:inherit}.toggle{position:absolute;inset-inline-end:.25rem;display:grid;place-items:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:none;color:inherit;cursor:pointer}.toggle:disabled{opacity:.4;cursor:default}:host([inline]) .toggle{display:none}",E=typeof HTMLElement>"u"?class{}:HTMLElement,n=class extends E{constructor(){super();this.ready=Promise.resolve(null);this.picker=null;this.extra={};this.gen=0;this.internals=this.attachInternals?.();let t=this.root=this.attachShadow({mode:"open",delegatesFocus:true});t.innerHTML=`<style>${d}${l}${x}</style><div class="wrap" part="wrap"><input part="input"><button type="button" class="toggle" part="toggle" aria-haspopup="dialog">${y}</button></div>`,this.wrap=t.querySelector(".wrap"),this.input=t.querySelector("input"),this.toggleBtn=t.querySelector("button"),this.iso=document.createElement("input"),this.toggleBtn.addEventListener("click",()=>this.picker?.toggle());}get value(){return this.iso.value}set value(t){this.setAttribute("value",t);}set options(t){this.extra={...this.extra,...t},this.apply();}open(){this.picker?.open();}close(){this.picker?.close();}toggle(){this.picker?.toggle();}connectedCallback(){this.ready=this.init();}disconnectedCallback(){this.teardown();}attributeChangedCallback(t,i,r){if(t==="placeholder")this.input.placeholder=r??"";else if(t==="label")r===null?this.input.removeAttribute("aria-label"):this.input.setAttribute("aria-label",r);else if(t==="disabled")this.setDisabled(r!==null);else if(this.picker)t==="value"?this.setValue(r):t==="inline"?(this.teardown(),this.ready=this.init()):this.apply();else return}formResetCallback(){this.setValue(this.getAttribute("value"));}formDisabledCallback(t){this.setDisabled(t);}opts(){let t={};for(let[i,[r,s]]of Object.entries(h))t[r]=s(this.getAttribute(i));return {...t,...this.extra}}async load({mode:t,enableTime:i}){await Promise.all([t&&f[t]?.(),i&&v()]);}async init(){if(this.picker)return this.picker;let t=++this.gen;if(await this.load(this.opts()),t!==this.gen||!this.isConnected)return null;let i=this.getAttribute("value"),r=this.picker=new a$1(this.input,{...this.opts(),showIcon:false,selected:i?u(i):null,isoInput:this.iso,anchor:this.wrap,appendTo:this.root});for(let s of w)r.addEventListener(s,g=>{s==="change"&&this.syncForm(),this.dispatchEvent(new CustomEvent(s,{detail:g.detail,bubbles:true,composed:true}));});return this.toggleBtn.setAttribute("aria-label",r.strings.label),this.setDisabled(this.hasAttribute("disabled")),this.syncForm(),r}teardown(){this.gen++,this.picker?.destroy(),this.picker=null;}async apply(){let t=this.opts();await this.load(t),this.picker&&(this.picker.setOptions(t),this.syncForm(),this.toggleBtn.setAttribute("aria-label",this.picker.strings.label));}setValue(t){this.picker?.setDate(t?u(t):[]),this.syncForm();}setDisabled(t){this.input.disabled=this.toggleBtn.disabled=t,t&&this.picker?.close();}syncForm(){this.internals?.setFormValue?.(this.iso.value||null);}};n.formAssociated=true,n.observedAttributes=[...Object.keys(h),"value","placeholder","label","disabled"];typeof customElements<"u"&&!customElements.get("x-datepicker")&&customElements.define("x-datepicker",n);export{n as XDatepicker};
76
+ `;var v={range:()=>import('./range.js'),multiple:()=>import('./multiple.js')},k=()=>import('./time.js'),o=e=>e??void 0,u=e=>e===null?void 0:Number(e),a=e=>e===null?void 0:e!=="false",w=e=>e===null?void 0:e.split(",").map(Number),h=e=>e.split(/\s*[/,;]\s*/).filter(Boolean),g={mode:["mode",o],locale:["locale",o],min:["minDate",o],max:["maxDate",o],"date-format":["dateFormat",o],"first-day":["firstDayOfWeek",u],position:["position",o],theme:["theme",o],"number-of-months":["numberOfMonths",u],"show-other-months":["showOtherMonths",a],"allow-input":["allowInput",a],"close-on-select":["closeOnSelect",a],"disabled-days":["disabledDaysOfWeek",w],"output-timestamp":["outputTimestamp",a],"enable-time":["enableTime",a],"value-in-utc":["valueInUtc",a],inline:["inline",a]},y=["change","open","close","monthChange","yearChange"],x='<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></svg>',E=":host{display:inline-block}:host([hidden]){display:none}.wrap{position:relative;display:flex;align-items:center}input{box-sizing:border-box;width:100%;padding:.5rem .75rem;padding-inline-start:2.25rem;font:inherit;color:var(--tp-fg);background:var(--tp-bg);border:1px solid var(--tp-border);border-radius:var(--tp-radius)}input:disabled{opacity:.6}input[aria-invalid=true]{border-color:#d1242f}.toggle{position:absolute;inset-inline-start:.25rem;display:grid;place-items:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:none;color:var(--tp-fg);cursor:pointer}.toggle:disabled{opacity:.4;cursor:default}:host([inline]) .toggle{display:none}",D=typeof HTMLElement>"u"?class{}:HTMLElement,n=class extends D{constructor(){super();this.ready=Promise.resolve(null);this.picker=null;this.extra={};this.gen=0;this.internals=this.attachInternals?.();let t=this.root=this.attachShadow({mode:"open",delegatesFocus:true});t.innerHTML=`<style>${l}${c}${E}</style><div class="wrap" part="wrap"><input part="input"><button type="button" class="toggle" part="toggle" aria-haspopup="dialog">${x}</button></div>`,this.wrap=t.querySelector(".wrap"),this.input=t.querySelector("input"),this.toggleBtn=t.querySelector("button"),this.iso=document.createElement("input"),this.toggleBtn.addEventListener("click",()=>this.picker?.toggle());}get value(){return this.iso.value}set value(t){this.setAttribute("value",t);}set options(t){this.extra={...this.extra,...t},this.apply();}open(){this.picker?.open();}close(){this.picker?.close();}toggle(){this.picker?.toggle();}connectedCallback(){this.ready=this.init();}disconnectedCallback(){this.teardown();}attributeChangedCallback(t,i,r){if(t==="placeholder")this.input.placeholder=r??"";else if(t==="label")r===null?this.input.removeAttribute("aria-label"):this.input.setAttribute("aria-label",r);else if(t==="disabled")this.setDisabled(r!==null);else if(this.picker)t==="value"?this.setValue(r):t==="inline"?(this.teardown(),this.ready=this.init()):this.apply();else return}formResetCallback(){this.setValue(this.getAttribute("value"));}formDisabledCallback(t){this.setDisabled(t);}opts(){let t={};for(let[i,[r,s]]of Object.entries(g))t[r]=s(this.getAttribute(i));return {...t,...this.extra}}async load({mode:t,enableTime:i}){await Promise.all([t&&v[t]?.(),i&&k()]);}async init(){if(this.picker)return this.picker;let t=++this.gen;if(await this.load(this.opts()),t!==this.gen||!this.isConnected)return null;let i=this.getAttribute("value"),r=this.picker=new a$1(this.input,{...this.opts(),showIcon:false,selected:i?h(i):null,isoInput:this.iso,anchor:this.wrap,appendTo:this.root});for(let s of y)r.addEventListener(s,b=>{s==="change"&&this.syncForm(),this.dispatchEvent(new CustomEvent(s,{detail:b.detail,bubbles:true,composed:true}));});return this.toggleBtn.setAttribute("aria-label",r.strings.label),this.setDisabled(this.hasAttribute("disabled")),this.syncForm(),r}teardown(){this.gen++,this.picker?.destroy(),this.picker=null;}async apply(){let t=this.opts();await this.load(t),this.picker&&(this.picker.setOptions(t),this.syncForm(),this.toggleBtn.setAttribute("aria-label",this.picker.strings.label));}setValue(t){this.picker?.setDate(t?h(t):[]),this.syncForm();}setDisabled(t){this.input.disabled=this.toggleBtn.disabled=t,t&&this.picker?.close();}syncForm(){this.internals?.setFormValue?.(this.iso.value||null);}};n.formAssociated=true,n.observedAttributes=[...Object.keys(g),"value","placeholder","label","disabled"];var M={"ten-protons-datepicker":class extends n{},"x-datepicker":n};if(typeof customElements<"u")for(let[e,p]of Object.entries(M))customElements.get(e)||customElements.define(e,p);export{n as XDatepicker};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ten-protons-datepicker",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Zero-dependency, accessible, tiny datepicker. Vanilla TypeScript + Web Component.",
5
5
  "keywords": [
6
6
  "datepicker",