@zeyos/zx 4.2.0 → 4.3.4
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/CHANGELOG.md +148 -1
- package/README.md +5 -3
- package/dist/types/components/calendar/calendar-model.d.ts +22 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -2
- package/dist/types/components/card-view/card-view.d.ts +1 -1
- package/dist/types/components/card-view/record-card.d.ts +47 -5
- package/dist/types/components/dialog/dialog.d.ts +6 -1
- package/dist/types/components/dropdown/dropdown.d.ts +5 -0
- package/dist/types/components/kanban-view/kanban-policy.d.ts +268 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +669 -24
- package/dist/types/components/launcher/launcher.d.ts +5 -0
- package/dist/types/components/message/message.d.ts +9 -1
- package/dist/types/components/modal/modal.d.ts +44 -7
- package/dist/types/components/sheet/sheet.d.ts +22 -12
- package/dist/types/core/component.d.ts +6 -1
- package/dist/types/core/overlay-host.d.ts +12 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/zx-zeyos.esm.js +173 -43
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +876 -230
- package/dist/zx.esm.js +2053 -338
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +2053 -338
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +250 -2
- package/docs/llms.md +67 -48
- package/docs/llms.txt +1 -3
- package/package.json +1 -2
- package/src/components/account-menu/account-menu.css +1 -1
- package/src/components/app-rail/app-rail.css +1 -1
- package/src/components/calendar/calendar-model.js +45 -0
- package/src/components/calendar/calendar.css +96 -34
- package/src/components/calendar/calendar.js +122 -29
- package/src/components/card-view/card-view.css +211 -9
- package/src/components/card-view/card-view.js +7 -7
- package/src/components/card-view/record-card.js +92 -19
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/context-menu/context-menu.js +5 -1
- package/src/components/date-picker/date-picker.css +18 -11
- package/src/components/datebox/date-range-box.css +4 -2
- package/src/components/datebox/datebox.css +4 -2
- package/src/components/dialog/dialog.css +4 -3
- package/src/components/dialog/dialog.js +2 -1
- package/src/components/dropdown/dropdown.css +4 -2
- package/src/components/dropdown/dropdown.js +7 -2
- package/src/components/kanban-view/kanban-policy.js +412 -0
- package/src/components/kanban-view/kanban-view.css +480 -36
- package/src/components/kanban-view/kanban-view.js +1514 -235
- package/src/components/launcher/launcher.css +10 -8
- package/src/components/launcher/launcher.js +9 -2
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +15 -25
- package/src/components/message/message.js +17 -11
- package/src/components/modal/modal.css +6 -4
- package/src/components/modal/modal.js +99 -25
- package/src/components/select/select.css +9 -7
- package/src/components/sheet/sheet.css +25 -7
- package/src/components/sheet/sheet.js +43 -23
- package/src/components/table/table.css +5 -3
- package/src/components/tag-picker/tag-picker.css +5 -3
- package/src/components/tooltip/tooltip.css +4 -2
- package/src/components/tooltip/tooltip.js +4 -1
- package/src/core/component.js +8 -3
- package/src/core/overlay-host.js +23 -0
- package/src/index.js +8 -3
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +83 -8
- package/DESIGN-SYSTEM.md +0 -182
package/docs/api.json
CHANGED
|
@@ -1468,7 +1468,7 @@
|
|
|
1468
1468
|
{
|
|
1469
1469
|
"name": "minCardWidth",
|
|
1470
1470
|
"type": "number|string",
|
|
1471
|
-
"default": "'
|
|
1471
|
+
"default": "'15rem'",
|
|
1472
1472
|
"description": "Responsive minimum card width."
|
|
1473
1473
|
},
|
|
1474
1474
|
{
|
|
@@ -2750,6 +2750,12 @@
|
|
|
2750
2750
|
"type": "Node|string|number|{toElement: () => Node|null}|null",
|
|
2751
2751
|
"default": "null",
|
|
2752
2752
|
"description": "Body content."
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
"name": "scope",
|
|
2756
|
+
"type": "Element|string|null",
|
|
2757
|
+
"default": "null",
|
|
2758
|
+
"description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
|
|
2753
2759
|
}
|
|
2754
2760
|
],
|
|
2755
2761
|
"events": [
|
|
@@ -3082,6 +3088,12 @@
|
|
|
3082
3088
|
"type": "boolean",
|
|
3083
3089
|
"default": "false",
|
|
3084
3090
|
"description": "Whether a click within the panel closes it."
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
"name": "scope",
|
|
3094
|
+
"type": "Element|string|null",
|
|
3095
|
+
"default": "null",
|
|
3096
|
+
"description": "Element whose nearest Zx theme scope owns the panel; defaults to the anchor."
|
|
3085
3097
|
}
|
|
3086
3098
|
],
|
|
3087
3099
|
"events": [
|
|
@@ -4318,6 +4330,132 @@
|
|
|
4318
4330
|
"default": "'local'",
|
|
4319
4331
|
"description": "Whether accepted moves update local data."
|
|
4320
4332
|
},
|
|
4333
|
+
{
|
|
4334
|
+
"name": "dragFrom",
|
|
4335
|
+
"type": "'card'|'handle'",
|
|
4336
|
+
"default": "'card'",
|
|
4337
|
+
"description": "Whether the whole card or only its handle starts a pointer drag. Touch drags from the card body require a long press so the board still scrolls."
|
|
4338
|
+
},
|
|
4339
|
+
{
|
|
4340
|
+
"name": "multiMove",
|
|
4341
|
+
"type": "boolean",
|
|
4342
|
+
"default": "true",
|
|
4343
|
+
"description": "Move the whole selection when a selected card is picked up."
|
|
4344
|
+
},
|
|
4345
|
+
{
|
|
4346
|
+
"name": "autoScroll",
|
|
4347
|
+
"type": "boolean",
|
|
4348
|
+
"default": "true",
|
|
4349
|
+
"description": "Scroll the board and columns while dragging near an edge."
|
|
4350
|
+
},
|
|
4351
|
+
{
|
|
4352
|
+
"name": "wipPolicy",
|
|
4353
|
+
"type": "'warn'|'block'",
|
|
4354
|
+
"default": "'warn'",
|
|
4355
|
+
"description": "Whether exceeding a limit warns or refuses."
|
|
4356
|
+
},
|
|
4357
|
+
{
|
|
4358
|
+
"name": "rules",
|
|
4359
|
+
"type": "KanbanRule[]",
|
|
4360
|
+
"default": "[]",
|
|
4361
|
+
"description": "Ordered visual card rules."
|
|
4362
|
+
},
|
|
4363
|
+
{
|
|
4364
|
+
"name": "search",
|
|
4365
|
+
"type": "string",
|
|
4366
|
+
"default": "''",
|
|
4367
|
+
"description": "Board search text."
|
|
4368
|
+
},
|
|
4369
|
+
{
|
|
4370
|
+
"name": "searchFields",
|
|
4371
|
+
"type": "string[]|null",
|
|
4372
|
+
"default": "null",
|
|
4373
|
+
"description": "Field ids searched; null searches visible fields plus the title and subtitle sources."
|
|
4374
|
+
},
|
|
4375
|
+
{
|
|
4376
|
+
"name": "filter",
|
|
4377
|
+
"type": "((record:KanbanRecord,index:number)=>boolean)|null",
|
|
4378
|
+
"default": "null",
|
|
4379
|
+
"description": "Extra card predicate."
|
|
4380
|
+
},
|
|
4381
|
+
{
|
|
4382
|
+
"name": "searchControl",
|
|
4383
|
+
"type": "boolean",
|
|
4384
|
+
"default": "false",
|
|
4385
|
+
"description": "Render the built-in board search control."
|
|
4386
|
+
},
|
|
4387
|
+
{
|
|
4388
|
+
"name": "history",
|
|
4389
|
+
"type": "boolean",
|
|
4390
|
+
"default": "true",
|
|
4391
|
+
"description": "Record committed local moves for undo and redo."
|
|
4392
|
+
},
|
|
4393
|
+
{
|
|
4394
|
+
"name": "historyLimit",
|
|
4395
|
+
"type": "number",
|
|
4396
|
+
"default": "50",
|
|
4397
|
+
"description": "Maximum remembered move steps."
|
|
4398
|
+
},
|
|
4399
|
+
{
|
|
4400
|
+
"name": "historyControls",
|
|
4401
|
+
"type": "boolean",
|
|
4402
|
+
"default": "false",
|
|
4403
|
+
"description": "Render built-in undo and redo controls."
|
|
4404
|
+
},
|
|
4405
|
+
{
|
|
4406
|
+
"name": "columnHeight",
|
|
4407
|
+
"type": "string|null",
|
|
4408
|
+
"default": "null",
|
|
4409
|
+
"description": "CSS length turning each column into a scroll region."
|
|
4410
|
+
},
|
|
4411
|
+
{
|
|
4412
|
+
"name": "allowAdd",
|
|
4413
|
+
"type": "boolean|((column:KanbanColumn,swimlane:KanbanSwimlane|null)=>boolean)",
|
|
4414
|
+
"default": "false",
|
|
4415
|
+
"description": "Render a per-column add control that emits `recordadd`."
|
|
4416
|
+
},
|
|
4417
|
+
{
|
|
4418
|
+
"name": "contextMenu",
|
|
4419
|
+
"type": "boolean|MenuItem[]|((context:KanbanCardContext)=>MenuItem[])",
|
|
4420
|
+
"default": "false",
|
|
4421
|
+
"description": "Card context menu. `true` offers move commands; an array or function appends application items."
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
"name": "renderCard",
|
|
4425
|
+
"type": "((context:KanbanCardContext)=>Node|null|undefined)|null",
|
|
4426
|
+
"default": "null",
|
|
4427
|
+
"description": "Replaces card content."
|
|
4428
|
+
},
|
|
4429
|
+
{
|
|
4430
|
+
"name": "renderColumnHeader",
|
|
4431
|
+
"type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
|
|
4432
|
+
"default": "null",
|
|
4433
|
+
"description": "Replaces column header content."
|
|
4434
|
+
},
|
|
4435
|
+
{
|
|
4436
|
+
"name": "renderSwimlaneHeader",
|
|
4437
|
+
"type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
|
|
4438
|
+
"default": "null",
|
|
4439
|
+
"description": "Replaces lane header content."
|
|
4440
|
+
},
|
|
4441
|
+
{
|
|
4442
|
+
"name": "renderColumnEmpty",
|
|
4443
|
+
"type": "((context:KanbanHeaderContext)=>Node|null|undefined)|null",
|
|
4444
|
+
"default": "null",
|
|
4445
|
+
"description": "Empty-column placeholder."
|
|
4446
|
+
},
|
|
4447
|
+
{
|
|
4448
|
+
"name": "renderDragPreview",
|
|
4449
|
+
"type": "((context:{records:KanbanRecord[],ids:unknown[],count:number})=>Node|null|undefined)|null",
|
|
4450
|
+
"default": "null",
|
|
4451
|
+
"description": "Replaces the floating drag preview."
|
|
4452
|
+
},
|
|
4453
|
+
{
|
|
4454
|
+
"name": "labels",
|
|
4455
|
+
"type": "Record<string,string>",
|
|
4456
|
+
"default": "{}",
|
|
4457
|
+
"description": "Overrides for every visible and announced string."
|
|
4458
|
+
},
|
|
4321
4459
|
{
|
|
4322
4460
|
"name": "columnOrder",
|
|
4323
4461
|
"type": "string[]",
|
|
@@ -4373,10 +4511,30 @@
|
|
|
4373
4511
|
"payload": "Record<string,unknown>",
|
|
4374
4512
|
"description": ""
|
|
4375
4513
|
},
|
|
4514
|
+
{
|
|
4515
|
+
"name": "movereject",
|
|
4516
|
+
"payload": "Record<string,unknown>",
|
|
4517
|
+
"description": ""
|
|
4518
|
+
},
|
|
4376
4519
|
{
|
|
4377
4520
|
"name": "recordaction",
|
|
4378
4521
|
"payload": "Record<string,unknown>",
|
|
4379
4522
|
"description": ""
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
"name": "recordadd",
|
|
4526
|
+
"payload": "Record<string,unknown>",
|
|
4527
|
+
"description": ""
|
|
4528
|
+
},
|
|
4529
|
+
{
|
|
4530
|
+
"name": "searchchange",
|
|
4531
|
+
"payload": "{search:string}",
|
|
4532
|
+
"description": ""
|
|
4533
|
+
},
|
|
4534
|
+
{
|
|
4535
|
+
"name": "historychange",
|
|
4536
|
+
"payload": "{canUndo:boolean,canRedo:boolean,depth:{undo:number,redo:number}}",
|
|
4537
|
+
"description": ""
|
|
4380
4538
|
}
|
|
4381
4539
|
],
|
|
4382
4540
|
"methods": [
|
|
@@ -4480,7 +4638,73 @@
|
|
|
4480
4638
|
"name": "moveRecord",
|
|
4481
4639
|
"signature": "moveRecord(id, destination)",
|
|
4482
4640
|
"returns": "this",
|
|
4483
|
-
"description": "Proposes and, in local mode, commits a record move.
|
|
4641
|
+
"description": "Proposes and, in local mode, commits a record move. Equivalent to `moveRecords([id], …)`."
|
|
4642
|
+
},
|
|
4643
|
+
{
|
|
4644
|
+
"name": "moveRecords",
|
|
4645
|
+
"signature": "moveRecords(ids, destination)",
|
|
4646
|
+
"returns": "this",
|
|
4647
|
+
"description": "Proposes and, in local mode, commits an atomic move of one or more records. Destination eligibility, transition rules, and work-in-progress policy are all checked before a single cancelable `recordmove` event; the move commits completely or not at all, and the moving records keep their relative order at the destination."
|
|
4648
|
+
},
|
|
4649
|
+
{
|
|
4650
|
+
"name": "canUndo",
|
|
4651
|
+
"signature": "canUndo()",
|
|
4652
|
+
"returns": "boolean",
|
|
4653
|
+
"description": "Whether a committed local move can be reverted."
|
|
4654
|
+
},
|
|
4655
|
+
{
|
|
4656
|
+
"name": "canRedo",
|
|
4657
|
+
"signature": "canRedo()",
|
|
4658
|
+
"returns": "boolean",
|
|
4659
|
+
"description": "Whether a reverted local move can be reapplied."
|
|
4660
|
+
},
|
|
4661
|
+
{
|
|
4662
|
+
"name": "undo",
|
|
4663
|
+
"signature": "undo()",
|
|
4664
|
+
"returns": "this",
|
|
4665
|
+
"description": "Reverts the last committed local move, restoring the exact record order and selection."
|
|
4666
|
+
},
|
|
4667
|
+
{
|
|
4668
|
+
"name": "redo",
|
|
4669
|
+
"signature": "redo()",
|
|
4670
|
+
"returns": "this",
|
|
4671
|
+
"description": "Reapplies the last reverted local move."
|
|
4672
|
+
},
|
|
4673
|
+
{
|
|
4674
|
+
"name": "clearHistory",
|
|
4675
|
+
"signature": "clearHistory()",
|
|
4676
|
+
"returns": "this",
|
|
4677
|
+
"description": "Empties both history stacks."
|
|
4678
|
+
},
|
|
4679
|
+
{
|
|
4680
|
+
"name": "getSearch",
|
|
4681
|
+
"signature": "getSearch()",
|
|
4682
|
+
"returns": "string",
|
|
4683
|
+
"description": "Returns the current board search text."
|
|
4684
|
+
},
|
|
4685
|
+
{
|
|
4686
|
+
"name": "setSearch",
|
|
4687
|
+
"signature": "setSearch(search, [options])",
|
|
4688
|
+
"returns": "this",
|
|
4689
|
+
"description": "Narrows visible cards to those matching a search. Hidden records keep their position in the data, so a card dropped between two visible cards still lands between them."
|
|
4690
|
+
},
|
|
4691
|
+
{
|
|
4692
|
+
"name": "setFilter",
|
|
4693
|
+
"signature": "setFilter(filter)",
|
|
4694
|
+
"returns": "this",
|
|
4695
|
+
"description": "Replaces the extra card predicate. Pass null to clear it."
|
|
4696
|
+
},
|
|
4697
|
+
{
|
|
4698
|
+
"name": "getVisibleRecords",
|
|
4699
|
+
"signature": "getVisibleRecords()",
|
|
4700
|
+
"returns": "KanbanRecord[]",
|
|
4701
|
+
"description": "Returns the records currently rendered, in board order."
|
|
4702
|
+
},
|
|
4703
|
+
{
|
|
4704
|
+
"name": "setData",
|
|
4705
|
+
"signature": "setData(records)",
|
|
4706
|
+
"returns": "this",
|
|
4707
|
+
"description": "Replaces every record. History is dropped because its snapshots describe records the host has just replaced, and reinstating them would resurrect data the application no longer owns."
|
|
4484
4708
|
},
|
|
4485
4709
|
{
|
|
4486
4710
|
"name": "getViewState",
|
|
@@ -4587,6 +4811,12 @@
|
|
|
4587
4811
|
"type": "false|{move?: string, open?: string, close?: string}",
|
|
4588
4812
|
"default": null,
|
|
4589
4813
|
"description": "Visible keyboard hints, or false."
|
|
4814
|
+
},
|
|
4815
|
+
{
|
|
4816
|
+
"name": "scope",
|
|
4817
|
+
"type": "Element|string|null",
|
|
4818
|
+
"default": "null",
|
|
4819
|
+
"description": "Element whose nearest Zx theme scope owns an internally created launcher; defaults to the opener."
|
|
4590
4820
|
}
|
|
4591
4821
|
],
|
|
4592
4822
|
"events": [
|
|
@@ -4969,6 +5199,12 @@
|
|
|
4969
5199
|
"type": "boolean",
|
|
4970
5200
|
"default": "true",
|
|
4971
5201
|
"description": "Whether to render a close button."
|
|
5202
|
+
},
|
|
5203
|
+
{
|
|
5204
|
+
"name": "scope",
|
|
5205
|
+
"type": "Element|string|null",
|
|
5206
|
+
"default": "null",
|
|
5207
|
+
"description": "Element whose nearest Zx theme scope owns a floating toast."
|
|
4972
5208
|
}
|
|
4973
5209
|
],
|
|
4974
5210
|
"events": [],
|
|
@@ -5005,6 +5241,12 @@
|
|
|
5005
5241
|
"type": "boolean",
|
|
5006
5242
|
"default": "false",
|
|
5007
5243
|
"description": "Whether to destroy the modal after it closes."
|
|
5244
|
+
},
|
|
5245
|
+
{
|
|
5246
|
+
"name": "scope",
|
|
5247
|
+
"type": "Element|string|null",
|
|
5248
|
+
"default": "null",
|
|
5249
|
+
"description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
|
|
5008
5250
|
}
|
|
5009
5251
|
],
|
|
5010
5252
|
"events": [
|
|
@@ -6985,6 +7227,12 @@
|
|
|
6985
7227
|
"type": "boolean",
|
|
6986
7228
|
"default": "false",
|
|
6987
7229
|
"description": "Whether to destroy the sheet after it closes."
|
|
7230
|
+
},
|
|
7231
|
+
{
|
|
7232
|
+
"name": "scope",
|
|
7233
|
+
"type": "Element|string|null",
|
|
7234
|
+
"default": "null",
|
|
7235
|
+
"description": "Element whose nearest Zx theme scope owns a floating sheet; defaults to the opener."
|
|
6988
7236
|
}
|
|
6989
7237
|
],
|
|
6990
7238
|
"events": [
|
package/docs/llms.md
CHANGED
|
@@ -31,6 +31,8 @@ const t = new Table(target, options);
|
|
|
31
31
|
(always an object, e.g. `{ value, item }`).
|
|
32
32
|
- Every `emit` also dispatches a **bubbling, composed** `zx-<type>` `CustomEvent` on `t.el`, so
|
|
33
33
|
`t.el.addEventListener('zx-change', …)` and event delegation work.
|
|
34
|
+
- Mutation boundaries may opt into honoring cancellation of that mirrored DOM event; Kanban's
|
|
35
|
+
`zx-recordmove` does, so delegated hosts can veto a move without a component reference.
|
|
34
36
|
- `t.destroy()` aborts all listeners (one AbortController), unregisters the element, and removes an
|
|
35
37
|
owned root. `Component.from(el)` returns the component for a root element (replaces the legacy
|
|
36
38
|
`el.retrieve('com')`).
|
|
@@ -80,14 +82,19 @@ ambient canvas layer; material controls how surfaces respond to that light. The
|
|
|
80
82
|
applies to AppIcons, ordinary controls, persistent chrome, raised cards, and every transient overlay: Dropdown, ContextMenu,
|
|
81
83
|
MenuButton, AccountMenu, AppRail flyouts, Select, TagPicker, date pickers, tooltips, column menus,
|
|
82
84
|
and toasts use `--zx-color-overlay-surface`; Launcher, Modal, Dialog, and overlay Sheets use the
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
surfaces.
|
|
85
|
+
calmer `--zx-color-overlay-panel`. Both tiers combine a translucent base, responsive specular and
|
|
86
|
+
Aurora tint layers, backdrop filtering, and dedicated state/divider roles; the modal backdrop is a
|
|
87
|
+
separate material token so it does not erase the light the panel should refract. A docked Sheet is
|
|
88
|
+
layout rather than an overlay and becomes opaque, as do dense data surfaces. Glass and Deep glass
|
|
89
|
+
progressively raise translucency, blur, highlights, and elevation; reduced-transparency,
|
|
90
|
+
increased-contrast, forced-colour, and unsupported-filter environments restore solid surfaces.
|
|
91
|
+
Top-layer components remain DOM children of their nearest Zx theme scope, so a theme or material
|
|
92
|
+
set on an application region continues to inherit through its menus, dialogs, tooltips, and toasts.
|
|
87
93
|
|
|
88
94
|
Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
|
|
89
95
|
`--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-glass-chrome/surface`,
|
|
90
|
-
`--zx-color-overlay-surface/panel/border`,
|
|
96
|
+
`--zx-color-overlay-surface/panel/backdrop/border/hover/selected/divider/scrim`,
|
|
97
|
+
`--zx-overlay-surface-image/toast-image/panel-image`, `--zx-color-border(-strong/-control)`,
|
|
91
98
|
`--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
|
|
92
99
|
`--zx-color-danger/warning/success/info(+ -bg)`, `--zx-focus-ring`, `--zx-control-height/-radius`,
|
|
93
100
|
`--zx-space-*`, `--zx-radius-*`, `--zx-text-*`. Never use tier-1 palette tokens (`--zx-gray-*`,
|
|
@@ -248,28 +255,6 @@ reflection and ElementInternals form association: `<zx-toggle>`, `<zx-check-butt
|
|
|
248
255
|
`<zx-checklist>`, `<zx-datebox>`, `<zx-timebox>`, `<zx-search>`, `<zx-groupbox>`, `<zx-tabbox>`,
|
|
249
256
|
`<zx-table>`, `<zx-dialog>`.
|
|
250
257
|
|
|
251
|
-
## Conventions (editing component source)
|
|
252
|
-
|
|
253
|
-
Follow `AGENTS.md`. Key rules: never declare instance class fields for state used in `render()`
|
|
254
|
-
(render runs inside the base constructor before field initializers); route all DOM listeners
|
|
255
|
-
through `this.listen()`; no `innerHTML` outside `h.raw()`; express state via ARIA/`data-*`, not
|
|
256
|
-
state classes; small glyph buttons use the shared `.zx-icon-btn` utility; follow the named APG
|
|
257
|
-
pattern with `:focus-visible` rings and `prefers-reduced-motion` guards.
|
|
258
|
-
|
|
259
|
-
## File map & commands
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
src/components/<name>/ component + CSS src/core/ kernel (component, dom, http, i18n, date…)
|
|
263
|
-
website/ marketing page + docs.html (docs.js) specs/ per-component specs
|
|
264
|
-
website/demos/ per-component demo modules website/layouts/ application layout examples
|
|
265
|
-
tests/ node unit + smoke dist/ built bundles
|
|
266
|
-
|
|
267
|
-
npm run serve # http://127.0.0.1:8321/website/docs.html (no build)
|
|
268
|
-
npm test # node --test tests/unit/*.test.js + node tests/lint-tokens.js
|
|
269
|
-
npm run build # dist/: ESM, global, ZeyOS binding, and CSS assets
|
|
270
|
-
node tools/build-module-tokens.js # regenerate styles/tokens/modules.css from src/zeyos/modules.js
|
|
271
|
-
```
|
|
272
|
-
|
|
273
258
|
---
|
|
274
259
|
|
|
275
260
|
# Components
|
|
@@ -456,11 +441,13 @@ A native-dialog launcher for applications, recent records, commands, and grouped
|
|
|
456
441
|
Zx owns ranking, focus, request cancellation, and presentation; ZeyOS owns the catalogue,
|
|
457
442
|
permissions, recent-history/cache policy, and the route or command ultimately invoked.
|
|
458
443
|
|
|
459
|
-
- **Constructor** — `new Launcher(null, options)` owns a
|
|
444
|
+
- **Constructor** — `new Launcher(null, options)` owns a top-layer dialog in the opener's nearest
|
|
445
|
+
Zx theme scope;
|
|
460
446
|
`new Launcher(existingDialog, options)` enhances and later restores an existing `<dialog>`.
|
|
461
447
|
- **Options** — `items`, abortable `sources: [{id, label?, minQuery?, when?, order?, load(query, {signal})}]`,
|
|
462
448
|
`query`, `debounce: 250`, `minQuery: 0`, `maxResults: 100`, `placeholder`, `label`, `emptyText`,
|
|
463
|
-
`loadingText`, `shortcut: 'mod+k'|false`,
|
|
449
|
+
`loadingText`, `shortcut: 'mod+k'|false`, visible `hints` labels (or `false`), and `scope` to
|
|
450
|
+
explicitly select an element whose nearest Zx theme scope owns the launcher.
|
|
464
451
|
- **Items** — `{id, label, description?, keywords?, group?, icon?, badge?, value?, href?, target?,
|
|
465
452
|
invoke?, pinned?, disabled?, kind?, current?, when?, groupOrder?, itemOrder?}`. `icon` accepts a
|
|
466
453
|
bundled icon name, Node, or factory. Links stay native so modified and middle clicks work;
|
|
@@ -787,8 +774,10 @@ The shared floating region is fixed to the upper-right logical corner by default
|
|
|
787
774
|
messages rather than the page, and uses the translucent glass surface tokens with a solid fallback.
|
|
788
775
|
|
|
789
776
|
- **Statics** — `Message.info/success/warning/error(msg, opts)`,
|
|
790
|
-
`Message.show(msg, { kind, timeout=4000, closable })` → `{ close() }`,
|
|
791
|
-
`Message.progress(text)` → `{ update(pct, text?), done(), fail(text?) }`.
|
|
777
|
+
`Message.show(msg, { kind, timeout=4000, closable, scope? })` → `{ close() }`,
|
|
778
|
+
`Message.progress(text, {scope?})` → `{ update(pct, text?), done(), fail(text?) }`. `scope` is
|
|
779
|
+
an element or selector whose nearest Zx theme scope owns that floating region; without it the
|
|
780
|
+
focused opener supplies the scope.
|
|
792
781
|
- **Per-message options** — `kind: 'info'`, `timeout`, `closable: true`, `maxVisible: 5` (extras
|
|
793
782
|
queue, and hovering pauses dismissal).
|
|
794
783
|
<!-- /doc -->
|
|
@@ -797,10 +786,10 @@ messages rather than the page, and uses the translucent glass surface tokens wit
|
|
|
797
786
|
### Modal
|
|
798
787
|
|
|
799
788
|
Thin overlay on the native `<dialog>` element. The surface uses the shared, reading-sized overlay
|
|
800
|
-
panel material; the backdrop uses `--zx-color-
|
|
789
|
+
panel material; the backdrop uses `--zx-color-overlay-backdrop`, and focus returns to the opener on close.
|
|
801
790
|
|
|
802
791
|
- **Options** — `content`, `width: 'auto'`, `closable: true`, `lightDismiss: false`,
|
|
803
|
-
`destroyOnClose: false
|
|
792
|
+
`destroyOnClose: false`, `scope: null` (the opener's nearest Zx theme scope by default).
|
|
804
793
|
- **Methods** — `open()`, `close(result?)`, `setContent()`, `isOpen()`.
|
|
805
794
|
- **Events** — `open`, `close {result}`, `cancel` (Esc; preventable with `event.preventDefault()`).
|
|
806
795
|
<!-- /doc -->
|
|
@@ -832,7 +821,7 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
|
|
|
832
821
|
(`true | 'trap-focus' | false`), `backdrop: 'dim'` (`'dim' | 'blur' | 'none'`), `size: null`
|
|
833
822
|
(CSS length or px number, applied to whichever axis `side` implies), `closeButton: true`,
|
|
834
823
|
plus everything Dialog takes — `title`, `content`, `buttons`, `closable`, `lightDismiss`
|
|
835
|
-
(defaults to `true` here, unlike Modal), `destroyOnClose`.
|
|
824
|
+
(defaults to `true` here, unlike Modal), `destroyOnClose`, `scope`.
|
|
836
825
|
- **Methods** — `open()`, `close(result?)`, `setSide(side)`, `getSide()`, `setSize(size)`,
|
|
837
826
|
`isModal()`, `isDocked()`, `isOpen()`, `getSize()`, `snapTo(target)`, plus Dialog's `setTitle()`, `setContent()`,
|
|
838
827
|
`setButtons()`, `addView()`, `showView()`.
|
|
@@ -846,10 +835,12 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
|
|
|
846
835
|
across a handoff: the element moves and reopens in place, so DOM state and listeners survive,
|
|
847
836
|
and no `open`/`close` is emitted for what is only a change of address.
|
|
848
837
|
- **Modality** — `true` delegates focus containment, page inertness, Escape, and the backdrop to
|
|
849
|
-
the browser through `showModal()`. `'trap-focus'` and `false`
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`
|
|
838
|
+
the browser through `showModal()`. Floating `'trap-focus'` and `false` sheets use a manual
|
|
839
|
+
Popover top layer, so they stay viewport-anchored even inside a transformed or clipped theme
|
|
840
|
+
scope while the page keeps scrolling and stays interactive. Escape and outside-click are
|
|
841
|
+
re-implemented, and `'trap-focus'` adds `focusTrap()` on top. Their top-layer backdrop is
|
|
842
|
+
explicitly transparent and non-filtering; `backdrop` is meaningful only when `modal` is `true`.
|
|
843
|
+
A docked Sheet remains a native non-modal dialog in its Dock's flow.
|
|
853
844
|
- **Resizing and detents** — `resizable` makes the inner edge draggable between `min` and `max`.
|
|
854
845
|
`snap` gives it detents: a number at or below 1 is a fraction of the viewport along the sheet's
|
|
855
846
|
axis, anything larger is pixels, and a string is any CSS length (`'320px'`, `'90%'`). With
|
|
@@ -920,12 +911,14 @@ alone, `new SheetStack({…})`.
|
|
|
920
911
|
### Dropdown
|
|
921
912
|
|
|
922
913
|
Generic anchored popover in the top layer, constructed as `new Dropdown(anchor, options)`. It
|
|
923
|
-
light-dismisses on an outside click or Esc,
|
|
914
|
+
light-dismisses on an outside click or Esc, mirrors `aria-expanded` onto the anchor, and stays a
|
|
915
|
+
DOM child of the anchor's nearest Zx theme scope while the browser lifts it into the top layer.
|
|
924
916
|
|
|
925
917
|
- **Options** — `content`,
|
|
926
918
|
`placement: 'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'` plus the matching
|
|
927
919
|
`left*` and `right*` placements, `offset: 4`,
|
|
928
|
-
`matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false
|
|
920
|
+
`matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false`, `scope: null` (the
|
|
921
|
+
anchor's nearest Zx theme scope by default).
|
|
929
922
|
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setContent()`, `getPanel()`.
|
|
930
923
|
- **Events** — `open`, `close`.
|
|
931
924
|
<!-- /doc -->
|
|
@@ -1138,8 +1131,15 @@ portable state as TableView and KanbanView.
|
|
|
1138
1131
|
`previewAlt`, `link`, `actions`, `groupBy`, `groupOrder`, `minCardWidth`, `maxColumns`,
|
|
1139
1132
|
`variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, and `label`.
|
|
1140
1133
|
- **Cards** — each record is a focusable `<li>` with a native title link, separate secondary
|
|
1141
|
-
actions, optional native multi-select checkbox, labelled metadata, and
|
|
1142
|
-
|
|
1134
|
+
actions, optional native multi-select checkbox, labelled metadata, and a global ARIA selection
|
|
1135
|
+
description that retains valid listitem semantics.
|
|
1136
|
+
CardView uses a dense collection rhythm: titles clamp to two lines, previews stay at
|
|
1137
|
+
thumbnail/avatar scale, and visible fields become compact labelled metadata chips. Title/subtitle
|
|
1138
|
+
fields are not duplicated in metadata unless their field sets `duplicate:true`.
|
|
1139
|
+
- **Shared card slots** — a visible field may set
|
|
1140
|
+
`view.card.slot: 'eyebrow-start'|'eyebrow-end'|'title-prefix'`. These slots create a semantic
|
|
1141
|
+
eyebrow row or put labelled content immediately before (and outside) the title heading/link.
|
|
1142
|
+
Unknown slots remain ordinary metadata; visibility and field order still control rendering.
|
|
1143
1143
|
- **Media and URLs** — `preview` accepts a field ID or callback returning an image URL,
|
|
1144
1144
|
`{src,alt,fit}`, Node, or null. Executable/non-image schemes are rejected and image failures show
|
|
1145
1145
|
a stable fallback. Title/action links are normalized; `_blank` links receive `noopener`.
|
|
@@ -1163,14 +1163,25 @@ cards, advisory work-in-progress limits, and equivalent pointer/programmatic/key
|
|
|
1163
1163
|
columns may also declare an advisory `limit`.
|
|
1164
1164
|
- **Methods** — configure/get/move columns and swim lanes, collapse either axis, and
|
|
1165
1165
|
`moveRecord(id,{column?,lane?,index?})`. Board-specific order/collapse joins the common view
|
|
1166
|
-
state; record order, movement history, and selection do not.
|
|
1166
|
+
state; record order, movement history, and selection do not. Saved preferences for data-derived
|
|
1167
|
+
axes survive an empty/partial result set and reconcile as later records introduce their IDs.
|
|
1168
|
+
- **Dense board layout** — bounded muted columns use a stable 20rem rail with native horizontal
|
|
1169
|
+
overflow, 48px headings, compact counts, 8–12px card padding, and 8px card gaps. Surface cards
|
|
1170
|
+
use an optional square thumbnail and a two-tier hierarchy from shared card field slots:
|
|
1171
|
+
`eyebrow-start`, `eyebrow-end`, and `title-prefix`. Unslotted metadata stays as quiet inline
|
|
1172
|
+
facts. Titles clamp visually to two lines while retaining their complete accessible text. Move
|
|
1173
|
+
and action controls reveal on hover/focus/selection/grab, and remain visible on touch devices.
|
|
1167
1174
|
- **Movement boundary** — `recordmove {record,id,from,to,column,swimlane,limitExceeded}` is
|
|
1168
|
-
cancelable and fires before a commit/request
|
|
1169
|
-
|
|
1170
|
-
|
|
1175
|
+
cancelable and fires before a commit/request; either the component event or bubbling
|
|
1176
|
+
`zx-recordmove` mirror may veto it. `accept` is a hard eligibility gate; WIP is visual advice.
|
|
1177
|
+
Local mode clones the moved record and never mutates host data, and a manual local move clears an
|
|
1178
|
+
active local sort so saved state matches visible order. External mode emits only, for server
|
|
1179
|
+
validation and optimistic rollback owned by ZeyOS.
|
|
1171
1180
|
- **Keyboard** — focus the move handle; Enter/Space grabs or drops, Escape cancels, Left/Right
|
|
1172
1181
|
chooses a column, Up/Down chooses a bucket-relative position, and Alt+Up/Down chooses a swim
|
|
1173
|
-
lane. A polite live region announces every target
|
|
1182
|
+
lane. A polite live region announces every target, result, and reached/exceeded WIP limit.
|
|
1183
|
+
Collapse keeps focus on its control, including when a programmatic collapse hides the focused
|
|
1184
|
+
card. Native drag is an enhancement.
|
|
1174
1185
|
<!-- /doc -->
|
|
1175
1186
|
|
|
1176
1187
|
<!-- doc:grid -->
|
|
@@ -1243,7 +1254,9 @@ windowed loader.
|
|
|
1243
1254
|
HTML, and suspicious CSS colour strings are ignored.
|
|
1244
1255
|
- **Methods** — `getEvents()`, `setEvents()`, `addEvent()`, `updateEvent()`, `removeEvent()`,
|
|
1245
1256
|
`getDate()`, `setDate()`, `getView()`, `setView()`, `prev()`, `next()`, `today()`,
|
|
1246
|
-
`setLoading()`, `focusEvent()`, `destroy()`.
|
|
1257
|
+
`setLoading()`, `focusEvent()`, `destroy()`. `prev()`/`next()` step one visible period and clamp
|
|
1258
|
+
the day of month into the target month, so a 29th–31st anchor cannot skip a shorter month; the
|
|
1259
|
+
same arithmetic is exported as `calendarPageDate()` and `addCalendarMonths()`.
|
|
1247
1260
|
- **Events** — `dateschange {view,start,end}`, `viewchange {view,oldView}`, `eventclick`,
|
|
1248
1261
|
`eventdblclick`, `dateclick {date,allDay,view}`, `select {start,end,allDay,view}`,
|
|
1249
1262
|
`new {date,view}`, `moreclick {date,count}`, `eventschange {events}`, and cancelable
|
|
@@ -1252,6 +1265,12 @@ windowed loader.
|
|
|
1252
1265
|
idempotent, version-guarded `detail.revert()` if persistence fails. With `optimistic: false`, the
|
|
1253
1266
|
component stays controlled: it emits the proposal without changing data, and the application
|
|
1254
1267
|
applies an accepted response through `updateEvent()` or `setEvents()`. Zx does not send requests.
|
|
1268
|
+
- **View layout** — agenda days keep every event in one vertically stacked content column. Month
|
|
1269
|
+
uses a continuous seven-day grid with full-height day boundaries and event lanes overlaid below
|
|
1270
|
+
the date labels; today is indicated with accent text instead of a decorative edge highlight.
|
|
1271
|
+
- **Pointer editing** — a timed move preserves the point where the event was grabbed and snaps the
|
|
1272
|
+
resulting start to `slotDuration`. The lower grip stretches the event in its original day column,
|
|
1273
|
+
while pointer capture keeps either interaction active if the pointer leaves the event surface.
|
|
1255
1274
|
- **Keyboard** — time slots and date grids use arrow-key navigation, Home/End, PageUp/PageDown,
|
|
1256
1275
|
Enter/Space activation, and roving focus. On an editable event, Space grabs it, arrows propose a
|
|
1257
1276
|
slot/day move (or change the focused resize grip), Space drops, and Escape cancels. Live status
|
package/docs/llms.txt
CHANGED
|
@@ -16,9 +16,7 @@ exports and as `window.zx.*`.
|
|
|
16
16
|
- [Documentation site](https://zx.zeyos.com/docs.html): getting started, every component with a live demo and its JavaScript source, and the application layouts. Machine index: [llms.txt](https://zx.zeyos.com/llms.txt).
|
|
17
17
|
- [README](../README.md): what Zx is, quick start (ESM + global script), component index, theming, and browser support.
|
|
18
18
|
- [API reference](llms.md): per-component constructor options, methods, and events for all public components plus the core helpers.
|
|
19
|
-
- [Skill](https://zx.zeyos.com/skills/zx/SKILL.md): task-oriented guide — mental model, recipes, ZeyOS HTTP client, theming, gotchas, commands.
|
|
20
19
|
- [Changelog](../CHANGELOG.md): what changed in each release, including behaviour changes an upgrade has to account for.
|
|
21
|
-
- [Contributor contract](https://github.com/zeyos/zx/blob/main/AGENTS.md): conventions for editing component source (token rules, render-time state, listeners, ARIA/APG).
|
|
22
20
|
|
|
23
21
|
## Components
|
|
24
22
|
- Inputs: `button()`, `buttonGroup()`, `badge()`/`badgeGroup()`, `CheckButton`,
|
|
@@ -52,7 +50,7 @@ exports and as `window.zx.*`.
|
|
|
52
50
|
- Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
|
|
53
51
|
|
|
54
52
|
## Theming
|
|
55
|
-
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share
|
|
53
|
+
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share responsive compact glass; Launcher, Modal, Dialog, and floating Sheets share a calmer reading panel. Both preserve scoped theme inheritance and refract Aurora/page content; docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
|
|
56
54
|
|
|
57
55
|
## Talking to ZeyOS
|
|
58
56
|
Use the dedicated ZeyOS client library **`@zeyos/client`** (`npm install @zeyos/client`) for ZeyOS business data: `createZeyosClient({ platform, auth })`, typed `client.api.list/get/create/update<Resource>` over the full OpenAPI surface, and `normalizeListResult`. Zx accepts the client by injection and does not bundle it. See the `website/layouts/zeyos-invoices.layout.js` layout for a Zx-components-on-@zeyos/client reference. Zx also ships a minimal `Http`/`zeyosService` for ad-hoc in-app `remotecall`, but `@zeyos/client` is the recommended default.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeyos/zx",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"description": "Dependency-free JavaScript implementation of the Xenon Design System.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,7 +65,6 @@
|
|
|
65
65
|
"docs/api.json",
|
|
66
66
|
"CHANGELOG.md",
|
|
67
67
|
"README.md",
|
|
68
|
-
"DESIGN-SYSTEM.md",
|
|
69
68
|
"LICENSE"
|
|
70
69
|
],
|
|
71
70
|
"engines": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
min-inline-size: 220px;
|
|
61
61
|
gap: 2px;
|
|
62
62
|
margin: calc(-1 * var(--zx-space-1)) calc(-1 * var(--zx-space-1)) var(--zx-space-1);
|
|
63
|
-
border-block-end: 1px solid var(--zx-color-
|
|
63
|
+
border-block-end: 1px solid var(--zx-color-overlay-divider);
|
|
64
64
|
padding: var(--zx-space-3);
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
}
|
|
102
102
|
.zx-app-rail__flyout-item:hover,
|
|
103
103
|
.zx-app-rail__flyout-item:focus-visible,
|
|
104
|
-
.zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-
|
|
104
|
+
.zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-overlay-hover); color: var(--zx-color-accent); }
|
|
105
105
|
.zx-app-rail__flyout-item-icon { display: inline-flex; grid-column: 1; }
|
|
106
106
|
.zx-app-rail__flyout-item-label { grid-column: 2; }
|
|
107
107
|
.zx-app-rail__flyout-item-badge { grid-column: 3; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
|
|
@@ -70,6 +70,51 @@ export function calendarRange(view, anchor, options = {}) {
|
|
|
70
70
|
return { start, end };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Returns the anchor date one visible period away from `anchor`.
|
|
75
|
+
*
|
|
76
|
+
* Month and year steps clamp the day of month into the target month instead of letting it
|
|
77
|
+
* overflow: `setMonth()` alone turns 31 January into 3 March, which makes February unreachable
|
|
78
|
+
* from a month-end anchor and leaves `prev()` on 31 March landing back inside March.
|
|
79
|
+
* @param {CalendarView} view Current view.
|
|
80
|
+
* @param {Date} anchor Current anchor date.
|
|
81
|
+
* @param {number} direction -1 for the previous period, 1 for the next.
|
|
82
|
+
* @param {{agendaDays?: number}} [options={}] Paging options.
|
|
83
|
+
* @returns {Date}
|
|
84
|
+
*/
|
|
85
|
+
export function calendarPageDate(view, anchor, direction, options = {}) {
|
|
86
|
+
const date = validDate(anchor, 'Calendar date');
|
|
87
|
+
const step = integer(direction, 'direction');
|
|
88
|
+
if (view === 'day') date.setDate(date.getDate() + step);
|
|
89
|
+
else if (view === 'agenda') {
|
|
90
|
+
date.setDate(date.getDate() + step * positiveInteger(options.agendaDays ?? 14, 'agendaDays'));
|
|
91
|
+
} else if (view === 'week') date.setDate(date.getDate() + step * 7);
|
|
92
|
+
else if (view === 'month') return addCalendarMonths(date, step);
|
|
93
|
+
else if (view === 'year') return addCalendarMonths(date, step * 12);
|
|
94
|
+
else throw new RangeError(`Unknown calendar view: ${view}`);
|
|
95
|
+
return date;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Adds whole months, clamping the day of month to the last day the target month actually has.
|
|
100
|
+
* @param {Date} value Local date.
|
|
101
|
+
* @param {number} months Months to add.
|
|
102
|
+
* @returns {Date}
|
|
103
|
+
*/
|
|
104
|
+
export function addCalendarMonths(value, months) {
|
|
105
|
+
const date = validDate(value, 'Calendar date');
|
|
106
|
+
const day = date.getDate();
|
|
107
|
+
date.setDate(1);
|
|
108
|
+
date.setMonth(date.getMonth() + integer(months, 'month amount'));
|
|
109
|
+
date.setDate(Math.min(day, daysInCalendarMonth(date.getFullYear(), date.getMonth())));
|
|
110
|
+
return date;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @param {number} year @param {number} month @returns {number} */
|
|
114
|
+
function daysInCalendarMonth(year, month) {
|
|
115
|
+
return new Date(year, month + 1, 0).getDate();
|
|
116
|
+
}
|
|
117
|
+
|
|
73
118
|
/**
|
|
74
119
|
* Normalizes caller records atomically into the Calendar event contract.
|
|
75
120
|
* @param {Record<string, any>[]} records Source records.
|