@ds-mo/ui 14.35.0 → 14.36.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/dist/.package-ready.json +1 -1
- package/dist/agent-patterns.json +1 -1
- package/dist/agent.json +1105 -187
- package/dist/angular/ds-button-inverted.d.ts +17 -0
- package/dist/angular/ds-button-inverted.js +46 -0
- package/dist/angular/ds-calendar.d.ts +15 -0
- package/dist/angular/ds-calendar.js +43 -0
- package/dist/angular/ds-field.d.ts +1 -1
- package/dist/angular/ds-field.js +1 -1
- package/dist/angular/ds-input-date.d.ts +15 -0
- package/dist/angular/ds-input-date.js +43 -0
- package/dist/angular/ds-input-time.d.ts +15 -0
- package/dist/angular/ds-input-time.js +43 -0
- package/dist/angular/ds-input.d.ts +1 -1
- package/dist/angular/ds-input.js +3 -3
- package/dist/angular/ds-select.d.ts +1 -1
- package/dist/angular/ds-select.js +3 -3
- package/dist/angular/ds-time-picker.d.ts +15 -0
- package/dist/angular/ds-time-picker.js +43 -0
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +5 -0
- package/dist/angular/proxies.d.ts +5 -0
- package/dist/angular/proxies.js +5 -0
- package/dist/angular/text-value-accessor.d.ts +1 -1
- package/dist/angular/text-value-accessor.js +2 -2
- package/dist/components/ds-agent-activity.js +1 -1
- package/dist/components/ds-agent-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-page-title.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-inverted.d.ts +11 -0
- package/dist/components/ds-button-inverted.js +2 -0
- package/dist/components/ds-button-inverted.js.map +1 -0
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/ds-calendar.d.ts +11 -0
- package/dist/components/ds-calendar.js +2 -0
- package/dist/components/ds-calendar.js.map +1 -0
- package/dist/components/ds-card-action-center.js +1 -1
- package/dist/components/ds-card-chart.js +1 -1
- package/dist/components/ds-card-navigation.js +1 -1
- package/dist/components/ds-card-navigation.js.map +1 -1
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-card-settings-scope.js +1 -1
- package/dist/components/ds-card-settings-scope.js.map +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart.js +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-item.js.map +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-divider.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-filter-menu.js +1 -1
- package/dist/components/ds-input-date.d.ts +11 -0
- package/dist/components/ds-input-date.js +2 -0
- package/dist/components/ds-input-date.js.map +1 -0
- package/dist/components/ds-input-time.d.ts +11 -0
- package/dist/components/ds-input-time.js +2 -0
- package/dist/components/ds-input-time.js.map +1 -0
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-message-actions.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-bubble.js.map +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-mobile-bar-nav.js +1 -1
- package/dist/components/ds-mobile-header.js +1 -1
- package/dist/components/ds-mobile-section-switcher.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-paper-texture.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-score.js +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-slider.js.map +1 -1
- package/dist/components/ds-swatch-picker.js +1 -1
- package/dist/components/ds-switch.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-table-filter.js +1 -1
- package/dist/components/ds-table-filter.js.map +1 -1
- package/dist/components/ds-table-group.js +1 -1
- package/dist/components/ds-table-preferences.js +1 -1
- package/dist/components/ds-table-preferences.js.map +1 -1
- package/dist/components/ds-table-saved-views.js +1 -1
- package/dist/components/ds-table-saved-views.js.map +1 -1
- package/dist/components/ds-table-search.js +1 -1
- package/dist/components/ds-table-search.js.map +1 -1
- package/dist/components/ds-table-sort.js +1 -1
- package/dist/components/ds-table-sort.js.map +1 -1
- package/dist/components/ds-table-toolbar.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-textarea.js +1 -1
- package/dist/components/ds-textarea.js.map +1 -1
- package/dist/components/ds-time-picker.d.ts +11 -0
- package/dist/components/ds-time-picker.js +2 -0
- package/dist/components/ds-time-picker.js.map +1 -0
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-tooltip-chart.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/{p-CI7VfYC3.js → p-2GVGhrh0.js} +2 -2
- package/dist/components/p-7O-2D29W.js +2 -0
- package/dist/components/p-7O-2D29W.js.map +1 -0
- package/dist/components/{p-CE540WZ7.js → p-8oRkhvjy.js} +2 -2
- package/dist/components/p-B27_chI7.js +2 -0
- package/dist/components/p-B27_chI7.js.map +1 -0
- package/dist/components/{p-xHGqrz_p.js → p-BOhpMUwO.js} +2 -2
- package/dist/components/{p-DdspY23v.js → p-BV5T1hzN.js} +2 -2
- package/dist/components/{p-DTDLlinw.js → p-BWfzaSc3.js} +2 -2
- package/dist/components/{p-CLGtPE0K.js → p-BgLI9JoT.js} +2 -2
- package/dist/components/{p-DGzn4k4h.js → p-C-dHtQMi.js} +2 -2
- package/dist/components/p-C0CE2v5D.js +2 -0
- package/dist/components/{p-yZRXPHDe.js → p-C2JnoaWI.js} +2 -2
- package/dist/components/{p-umAf0fBn.js → p-C4m8y9ki.js} +2 -2
- package/dist/components/{p-CAxDdYjh.js → p-CDJ2Ntcq.js} +2 -2
- package/dist/components/{p-DQQFN2sw.js → p-CMQUuGvl.js} +2 -2
- package/dist/components/{p-BQaO7cg_.js → p-CQ5RwY2C.js} +2 -2
- package/dist/components/p-CQIh2yRP.js +2 -0
- package/dist/components/{p-C1_3CVma.js.map → p-CQIh2yRP.js.map} +1 -1
- package/dist/components/{p-DEGNrPLe.js → p-CWPT3aOB.js} +2 -2
- package/dist/components/p-CWog26I_.js +2 -0
- package/dist/components/p-CWog26I_.js.map +1 -0
- package/dist/components/{p-beRFLHbB.js → p-CaNCGKEJ.js} +2 -2
- package/dist/components/p-CbSUyv5B.js +2 -0
- package/dist/components/{p-BINSI_KF.js.map → p-CbSUyv5B.js.map} +1 -1
- package/dist/components/{p-Dhw_W8nk.js → p-Cc3ZUQlJ.js} +2 -2
- package/dist/components/p-Cc3ZUQlJ.js.map +1 -0
- package/dist/components/{p-DFjIBS5B.js → p-Ce4zy8q1.js} +2 -2
- package/dist/components/{p-BcdHgPi5.js → p-CeXf9-X3.js} +2 -2
- package/dist/components/{p-omPm8_vx.js → p-Cga6Ex0E.js} +2 -2
- package/dist/components/{p-y06IxxoN.js → p-Cl9phSf0.js} +2 -2
- package/dist/components/{p-Dk-8Ju0Q.js → p-ClJhuG5Y.js} +2 -2
- package/dist/components/{p-BmFeeR4q.js → p-CqW2eyiF.js} +2 -2
- package/dist/components/p-Cus1Sigh.js +2 -0
- package/dist/components/{p-N5SHSVo2.js.map → p-Cus1Sigh.js.map} +1 -1
- package/dist/components/{p-CzbsTZt7.js → p-Cxr0IQjC.js} +2 -2
- package/dist/components/{p-CwqFJMvP.js → p-D7yBIPB6.js} +2 -2
- package/dist/components/p-D8rpKod8.js +2 -0
- package/dist/components/p-D8rpKod8.js.map +1 -0
- package/dist/components/{p-DtLQIMsa.js → p-DBwa1OXP.js} +2 -2
- package/dist/components/{p-BQsVI_vO.js → p-DK9Ex3aY.js} +2 -2
- package/dist/components/{p-CqlBlv55.js → p-DLXXGgmf.js} +2 -2
- package/dist/components/{p-BnzsBNUY.js → p-DVd1TIxi.js} +2 -2
- package/dist/components/p-DZ58Sg5a.js.map +1 -1
- package/dist/components/{p-CZOiTaNa.js → p-DgG9W9N4.js} +2 -2
- package/dist/components/{p-mpk_Mm8_.js → p-DkfM55x8.js} +2 -2
- package/dist/components/p-DkikNSSO.js +2 -0
- package/dist/components/p-DkikNSSO.js.map +1 -0
- package/dist/components/{p-C7CZ9pnP.js → p-Dryt3COt.js} +2 -2
- package/dist/components/{p-eo_pmPqp.js → p-Ds51RgCi.js} +2 -2
- package/dist/components/p-DtkLtKY0.js +2 -0
- package/dist/components/p-DtkLtKY0.js.map +1 -0
- package/dist/components/{p-C8ek-1SP.js → p-DvzI2A_M.js} +2 -2
- package/dist/components/p-J5x-QtlD.js +2 -0
- package/dist/components/{p-beCz9OnD.js.map → p-J5x-QtlD.js.map} +1 -1
- package/dist/components/{p-CSnsz3Eb.js → p-Jaqdfw4E.js} +2 -2
- package/dist/components/p-MQ36mEgn.js +2 -0
- package/dist/components/{p-UAIxh67Q.js.map → p-MQ36mEgn.js.map} +1 -1
- package/dist/components/{p-BnWxbhFP.js → p-PmcUwTQ_.js} +2 -2
- package/dist/components/p-TjXGRPik.js +2 -0
- package/dist/components/{p-B-9fdIWv.js.map → p-TjXGRPik.js.map} +1 -1
- package/dist/components/{p-Cq_Qpe_C.js → p-bM3LH3kc.js} +2 -2
- package/dist/components/{p-BPhbwXOv.js → p-ji09W9el.js} +2 -2
- package/dist/components/{p-n68x-ZKp.js → p-neWTRxmf.js} +2 -2
- package/dist/components/{p-BPlWEidl.js → p-rYePOPbJ.js} +2 -2
- package/dist/components/{p-D44g2Kw1.js → p-su6y4hLK.js} +2 -2
- package/dist/components/p-su6y4hLK.js.map +1 -0
- package/dist/components/{p-DC69DgAR.js → p-tNQ0YulU.js} +2 -2
- package/dist/components/{p-Cwz9TPVS.js → p-x1wf38q3.js} +2 -2
- package/dist/docs/components.json +6485 -3451
- package/dist/lib/utils/button-render.d.ts +1 -1
- package/dist/lib/utils/clock-time.d.ts +22 -0
- package/dist/lib/utils/date-filter-value.d.ts +4 -0
- package/dist/lib/utils/index.d.ts +3 -1
- package/dist/lib/utils/index.js +133 -0
- package/dist/lib/utils/index.js.map +4 -4
- package/dist/mcp-data/registry/bar-title.json +2 -2
- package/dist/mcp-data/registry/button-inverted.json +580 -0
- package/dist/mcp-data/registry/calendar.json +273 -0
- package/dist/mcp-data/registry/card-navigation.json +4 -2
- package/dist/mcp-data/registry/field.json +19 -14
- package/dist/mcp-data/registry/filter-menu.json +6 -10
- package/dist/mcp-data/registry/input-date.json +516 -0
- package/dist/mcp-data/registry/input-time.json +535 -0
- package/dist/mcp-data/registry/input.json +56 -8
- package/dist/mcp-data/registry/registry.json +1118 -185
- package/dist/mcp-data/registry/select.json +63 -7
- package/dist/mcp-data/registry/table-saved-views.json +6 -4
- package/dist/mcp-data/registry/textarea.json +2 -2
- package/dist/mcp-data/registry/time-picker.json +259 -0
- package/dist/react/components.d.ts +5 -0
- package/dist/react/components.js +5 -0
- package/dist/react/ds-button-inverted.d.ts +13 -0
- package/dist/react/ds-button-inverted.js +15 -0
- package/dist/react/ds-calendar.d.ts +12 -0
- package/dist/react/ds-calendar.js +12 -0
- package/dist/react/ds-field.d.ts +1 -1
- package/dist/react/ds-input-date.d.ts +12 -0
- package/dist/react/ds-input-date.js +12 -0
- package/dist/react/ds-input-time.d.ts +12 -0
- package/dist/react/ds-input-time.js +12 -0
- package/dist/react/ds-time-picker.d.ts +12 -0
- package/dist/react/ds-time-picker.js +12 -0
- package/dist/types/components/BarTitle/BarTitle.d.ts +1 -0
- package/dist/types/components/BarTitle/bar-title-action-chrome.d.ts +2 -0
- package/dist/types/components/ButtonInverted/ButtonInverted.d.ts +83 -0
- package/dist/types/components/ButtonInverted/index.d.ts +2 -0
- package/dist/types/components/Calendar/Calendar.d.ts +32 -0
- package/dist/types/components/Calendar/calendar-grid.d.ts +19 -0
- package/dist/types/components/Field/Field.d.ts +7 -2
- package/dist/types/components/FilterMenu/FilterMenu.d.ts +0 -10
- package/dist/types/components/Input/Input.d.ts +10 -0
- package/dist/types/components/InputDate/InputDate.d.ts +78 -0
- package/dist/types/components/InputTime/InputTime.d.ts +80 -0
- package/dist/types/components/Select/Select.d.ts +8 -1
- package/dist/types/components/TimePicker/TimePicker.d.ts +33 -0
- package/dist/types/components.d.ts +858 -27
- package/dist/types/utils/button-render.d.ts +1 -1
- package/dist/types/utils/clock-time.d.ts +23 -0
- package/dist/types/utils/date-filter-value.d.ts +4 -0
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/vue/components.d.ts +5 -0
- package/dist/vue/components.js +5 -0
- package/dist/vue/ds-button-inverted.d.ts +3 -0
- package/dist/vue/ds-button-inverted.js +33 -0
- package/dist/vue/ds-calendar.d.ts +3 -0
- package/dist/vue/ds-calendar.js +16 -0
- package/dist/vue/ds-input-date.d.ts +3 -0
- package/dist/vue/ds-input-date.js +31 -0
- package/dist/vue/ds-input-time.d.ts +3 -0
- package/dist/vue/ds-input-time.js +32 -0
- package/dist/vue/ds-input.js +2 -0
- package/dist/vue/ds-select.js +3 -0
- package/dist/vue/ds-time-picker.d.ts +3 -0
- package/dist/vue/ds-time-picker.js +16 -0
- package/package.json +1 -1
- package/dist/components/p-B-9fdIWv.js +0 -2
- package/dist/components/p-BINSI_KF.js +0 -2
- package/dist/components/p-BbfBvMRR.js +0 -2
- package/dist/components/p-BbfBvMRR.js.map +0 -1
- package/dist/components/p-C1_3CVma.js +0 -2
- package/dist/components/p-Cmj04E5Q.js +0 -2
- package/dist/components/p-Cmj04E5Q.js.map +0 -1
- package/dist/components/p-D44g2Kw1.js.map +0 -1
- package/dist/components/p-D_u8WhlU.js +0 -2
- package/dist/components/p-Dhw_W8nk.js.map +0 -1
- package/dist/components/p-FPZbS19l.js +0 -2
- package/dist/components/p-FPZbS19l.js.map +0 -1
- package/dist/components/p-N5SHSVo2.js +0 -2
- package/dist/components/p-QjkufCcn.js +0 -2
- package/dist/components/p-QjkufCcn.js.map +0 -1
- package/dist/components/p-UAIxh67Q.js +0 -2
- package/dist/components/p-beCz9OnD.js +0 -2
- package/dist/types/components/FilterMenu/filter-menu-calendar.d.ts +0 -13
- /package/dist/components/{p-CI7VfYC3.js.map → p-2GVGhrh0.js.map} +0 -0
- /package/dist/components/{p-CE540WZ7.js.map → p-8oRkhvjy.js.map} +0 -0
- /package/dist/components/{p-xHGqrz_p.js.map → p-BOhpMUwO.js.map} +0 -0
- /package/dist/components/{p-DdspY23v.js.map → p-BV5T1hzN.js.map} +0 -0
- /package/dist/components/{p-DTDLlinw.js.map → p-BWfzaSc3.js.map} +0 -0
- /package/dist/components/{p-CLGtPE0K.js.map → p-BgLI9JoT.js.map} +0 -0
- /package/dist/components/{p-DGzn4k4h.js.map → p-C-dHtQMi.js.map} +0 -0
- /package/dist/components/{p-D_u8WhlU.js.map → p-C0CE2v5D.js.map} +0 -0
- /package/dist/components/{p-yZRXPHDe.js.map → p-C2JnoaWI.js.map} +0 -0
- /package/dist/components/{p-umAf0fBn.js.map → p-C4m8y9ki.js.map} +0 -0
- /package/dist/components/{p-CAxDdYjh.js.map → p-CDJ2Ntcq.js.map} +0 -0
- /package/dist/components/{p-DQQFN2sw.js.map → p-CMQUuGvl.js.map} +0 -0
- /package/dist/components/{p-BQaO7cg_.js.map → p-CQ5RwY2C.js.map} +0 -0
- /package/dist/components/{p-DEGNrPLe.js.map → p-CWPT3aOB.js.map} +0 -0
- /package/dist/components/{p-beRFLHbB.js.map → p-CaNCGKEJ.js.map} +0 -0
- /package/dist/components/{p-DFjIBS5B.js.map → p-Ce4zy8q1.js.map} +0 -0
- /package/dist/components/{p-BcdHgPi5.js.map → p-CeXf9-X3.js.map} +0 -0
- /package/dist/components/{p-omPm8_vx.js.map → p-Cga6Ex0E.js.map} +0 -0
- /package/dist/components/{p-y06IxxoN.js.map → p-Cl9phSf0.js.map} +0 -0
- /package/dist/components/{p-Dk-8Ju0Q.js.map → p-ClJhuG5Y.js.map} +0 -0
- /package/dist/components/{p-BmFeeR4q.js.map → p-CqW2eyiF.js.map} +0 -0
- /package/dist/components/{p-CzbsTZt7.js.map → p-Cxr0IQjC.js.map} +0 -0
- /package/dist/components/{p-CwqFJMvP.js.map → p-D7yBIPB6.js.map} +0 -0
- /package/dist/components/{p-DtLQIMsa.js.map → p-DBwa1OXP.js.map} +0 -0
- /package/dist/components/{p-BQsVI_vO.js.map → p-DK9Ex3aY.js.map} +0 -0
- /package/dist/components/{p-CqlBlv55.js.map → p-DLXXGgmf.js.map} +0 -0
- /package/dist/components/{p-BnzsBNUY.js.map → p-DVd1TIxi.js.map} +0 -0
- /package/dist/components/{p-CZOiTaNa.js.map → p-DgG9W9N4.js.map} +0 -0
- /package/dist/components/{p-mpk_Mm8_.js.map → p-DkfM55x8.js.map} +0 -0
- /package/dist/components/{p-C7CZ9pnP.js.map → p-Dryt3COt.js.map} +0 -0
- /package/dist/components/{p-eo_pmPqp.js.map → p-Ds51RgCi.js.map} +0 -0
- /package/dist/components/{p-C8ek-1SP.js.map → p-DvzI2A_M.js.map} +0 -0
- /package/dist/components/{p-CSnsz3Eb.js.map → p-Jaqdfw4E.js.map} +0 -0
- /package/dist/components/{p-BnWxbhFP.js.map → p-PmcUwTQ_.js.map} +0 -0
- /package/dist/components/{p-Cq_Qpe_C.js.map → p-bM3LH3kc.js.map} +0 -0
- /package/dist/components/{p-BPhbwXOv.js.map → p-ji09W9el.js.map} +0 -0
- /package/dist/components/{p-n68x-ZKp.js.map → p-neWTRxmf.js.map} +0 -0
- /package/dist/components/{p-BPlWEidl.js.map → p-rYePOPbJ.js.map} +0 -0
- /package/dist/components/{p-DC69DgAR.js.map → p-tNQ0YulU.js.map} +0 -0
- /package/dist/components/{p-Cwz9TPVS.js.map → p-x1wf38q3.js.map} +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "calendar",
|
|
4
|
+
"title": "Calendar",
|
|
5
|
+
"description": "Month calendar grid for choosing one calendar date or an inclusive date range, reused by form date fields and filter range selection.",
|
|
6
|
+
"type": "registry:ui",
|
|
7
|
+
"meta": {
|
|
8
|
+
"library": "@ds-mo/ui",
|
|
9
|
+
"distribution": "npm",
|
|
10
|
+
"storybook": "https://zainadeel.github.io/compomo/?path=/story/calendar",
|
|
11
|
+
"source": "src/wc/components/Calendar/Calendar.tsx",
|
|
12
|
+
"intentStatus": "complete",
|
|
13
|
+
"intent": {
|
|
14
|
+
"audience": "general",
|
|
15
|
+
"status": "experimental",
|
|
16
|
+
"summary": "Month calendar grid for choosing one calendar date or an inclusive date range, reused by form date fields and filter range selection.",
|
|
17
|
+
"useWhen": [
|
|
18
|
+
"A user picks one calendar day from a month grid.",
|
|
19
|
+
"A user builds an inclusive start and end date by choosing two days.",
|
|
20
|
+
"A date field or filter needs the shared month navigation, weekday headings, and selected-day treatment."
|
|
21
|
+
],
|
|
22
|
+
"avoidWhen": [
|
|
23
|
+
"The user types a date without opening a calendar; the owning field keeps that editable value.",
|
|
24
|
+
"The user chooses a relative preset such as last 7 days rather than fixed calendar days.",
|
|
25
|
+
"The popup also has to host filter categories, search, or apply actions; that surface stays with FilterMenu."
|
|
26
|
+
],
|
|
27
|
+
"alternatives": [
|
|
28
|
+
{
|
|
29
|
+
"when": "The date is a form field that can be typed or picked.",
|
|
30
|
+
"component": "component:ds-input-date",
|
|
31
|
+
"reason": "InputDate owns the editable field, form association, and the calendar popup."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"when": "The value is a clock time rather than a calendar date.",
|
|
35
|
+
"component": "component:ds-time-picker",
|
|
36
|
+
"reason": "TimePicker owns bounded hour, minute, and AM/PM lists with the same 32px tile geometry."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"when": "The user chooses relative presets or mixed filter families from one trigger.",
|
|
40
|
+
"component": "component:ds-filter-menu",
|
|
41
|
+
"reason": "FilterMenu owns relative date presets and hosts this calendar only for fixed ranges."
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"commonlyComposedWith": [
|
|
45
|
+
"component:ds-input-date",
|
|
46
|
+
"component:ds-filter-menu",
|
|
47
|
+
"component:ds-button-unfilled",
|
|
48
|
+
"component:ds-text"
|
|
49
|
+
],
|
|
50
|
+
"accessibility": [
|
|
51
|
+
"The month grid is labelled with the visible month and year. Days are grid cells with an accessible date name.",
|
|
52
|
+
"The grid keeps one tab stop. Arrow keys move by day or week, Home and End move within the week, and Page Up or Page Down change month.",
|
|
53
|
+
"In range mode, the first chosen day applies immediately as a one-day range. Hovering or focusing another day previews the inclusive span without changing the value until the second activation.",
|
|
54
|
+
"Month navigation buttons are named Previous month and Next month. Inactive calendars disable day and month controls."
|
|
55
|
+
],
|
|
56
|
+
"states": [
|
|
57
|
+
"Single selection paints the current value with the shared interaction-fill selected wash and primary text. A committed range paints inclusive days with the faint brand fill and both edges boldly.",
|
|
58
|
+
"Today uses emphasized primary text without becoming selected on its own.",
|
|
59
|
+
"Days outside the visible month remain in the six-week grid and use tertiary text. Optional min and max dates disable days outside that inclusive bound.",
|
|
60
|
+
"After the first range day is chosen, that day keeps the shared pressed overlay. Hovering or focusing toward another day paints the inclusive span with the shared hover overlay on top of the previous committed range, which stays visible until the replacement is completed. Completing the end day orders the two dates inclusively and replaces those overlays with brand fill."
|
|
61
|
+
],
|
|
62
|
+
"responsiveBehavior": [
|
|
63
|
+
"The grid fills the width supplied by its owner. Seven equal day columns and the shared 4px gaps are owned here; the owner supplies the popup or pane width."
|
|
64
|
+
],
|
|
65
|
+
"frameworkCaveats": {
|
|
66
|
+
"customElements": [
|
|
67
|
+
"Listen for dsChange. Single mode emits YYYY-MM-DD. Range mode emits range:start/end using the shared date-filter string."
|
|
68
|
+
],
|
|
69
|
+
"react": [
|
|
70
|
+
"Use DsCalendar, bind value, and handle onDsChange."
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"references": [
|
|
74
|
+
{
|
|
75
|
+
"label": "Storybook examples",
|
|
76
|
+
"path": "src/wc/components/Calendar/Calendar.stories.ts"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"api": {
|
|
81
|
+
"props": {
|
|
82
|
+
"autoFocus": {
|
|
83
|
+
"type": "boolean",
|
|
84
|
+
"resolvedType": "boolean",
|
|
85
|
+
"attribute": "auto-focus",
|
|
86
|
+
"default": "false",
|
|
87
|
+
"required": false,
|
|
88
|
+
"mutable": false,
|
|
89
|
+
"description": "Move keyboard focus into the grid after the calendar is rendered."
|
|
90
|
+
},
|
|
91
|
+
"isInactive": {
|
|
92
|
+
"type": "boolean",
|
|
93
|
+
"resolvedType": "boolean",
|
|
94
|
+
"attribute": "is-inactive",
|
|
95
|
+
"default": "false",
|
|
96
|
+
"required": false,
|
|
97
|
+
"mutable": false
|
|
98
|
+
},
|
|
99
|
+
"max": {
|
|
100
|
+
"type": "string | undefined",
|
|
101
|
+
"resolvedType": "string | undefined",
|
|
102
|
+
"attribute": "max",
|
|
103
|
+
"required": false,
|
|
104
|
+
"mutable": false
|
|
105
|
+
},
|
|
106
|
+
"min": {
|
|
107
|
+
"type": "string | undefined",
|
|
108
|
+
"resolvedType": "string | undefined",
|
|
109
|
+
"attribute": "min",
|
|
110
|
+
"required": false,
|
|
111
|
+
"mutable": false
|
|
112
|
+
},
|
|
113
|
+
"selectionMode": {
|
|
114
|
+
"type": "CalendarSelectionMode",
|
|
115
|
+
"resolvedType": "\"range\" | \"single\"",
|
|
116
|
+
"attribute": "selection-mode",
|
|
117
|
+
"default": "'single'",
|
|
118
|
+
"required": false,
|
|
119
|
+
"mutable": false,
|
|
120
|
+
"description": "Single date emits YYYY-MM-DD. Range emits the shared `range:start/end` string."
|
|
121
|
+
},
|
|
122
|
+
"value": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"resolvedType": "string",
|
|
125
|
+
"attribute": "value",
|
|
126
|
+
"default": "''",
|
|
127
|
+
"required": false,
|
|
128
|
+
"mutable": false
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"events": [
|
|
132
|
+
{
|
|
133
|
+
"name": "dsChange",
|
|
134
|
+
"detail": "string",
|
|
135
|
+
"bubbles": true,
|
|
136
|
+
"cancelable": true,
|
|
137
|
+
"composed": true
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"methods": [
|
|
141
|
+
{
|
|
142
|
+
"name": "setFocus",
|
|
143
|
+
"signature": "setFocus() => Promise<void>"
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"slots": []
|
|
147
|
+
},
|
|
148
|
+
"props": {
|
|
149
|
+
"autoFocus": {
|
|
150
|
+
"type": "boolean",
|
|
151
|
+
"resolvedType": "boolean",
|
|
152
|
+
"attribute": "auto-focus",
|
|
153
|
+
"default": "false",
|
|
154
|
+
"required": false,
|
|
155
|
+
"mutable": false,
|
|
156
|
+
"description": "Move keyboard focus into the grid after the calendar is rendered."
|
|
157
|
+
},
|
|
158
|
+
"isInactive": {
|
|
159
|
+
"type": "boolean",
|
|
160
|
+
"resolvedType": "boolean",
|
|
161
|
+
"attribute": "is-inactive",
|
|
162
|
+
"default": "false",
|
|
163
|
+
"required": false,
|
|
164
|
+
"mutable": false
|
|
165
|
+
},
|
|
166
|
+
"max": {
|
|
167
|
+
"type": "string | undefined",
|
|
168
|
+
"resolvedType": "string | undefined",
|
|
169
|
+
"attribute": "max",
|
|
170
|
+
"required": false,
|
|
171
|
+
"mutable": false
|
|
172
|
+
},
|
|
173
|
+
"min": {
|
|
174
|
+
"type": "string | undefined",
|
|
175
|
+
"resolvedType": "string | undefined",
|
|
176
|
+
"attribute": "min",
|
|
177
|
+
"required": false,
|
|
178
|
+
"mutable": false
|
|
179
|
+
},
|
|
180
|
+
"selectionMode": {
|
|
181
|
+
"type": "CalendarSelectionMode",
|
|
182
|
+
"resolvedType": "\"range\" | \"single\"",
|
|
183
|
+
"attribute": "selection-mode",
|
|
184
|
+
"default": "'single'",
|
|
185
|
+
"required": false,
|
|
186
|
+
"mutable": false,
|
|
187
|
+
"description": "Single date emits YYYY-MM-DD. Range emits the shared `range:start/end` string."
|
|
188
|
+
},
|
|
189
|
+
"value": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"resolvedType": "string",
|
|
192
|
+
"attribute": "value",
|
|
193
|
+
"default": "''",
|
|
194
|
+
"required": false,
|
|
195
|
+
"mutable": false
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"events": [
|
|
199
|
+
{
|
|
200
|
+
"name": "dsChange",
|
|
201
|
+
"detail": "string",
|
|
202
|
+
"bubbles": true,
|
|
203
|
+
"cancelable": true,
|
|
204
|
+
"composed": true
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"methods": [
|
|
208
|
+
{
|
|
209
|
+
"name": "setFocus",
|
|
210
|
+
"signature": "setFocus() => Promise<void>"
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
"slots": [],
|
|
214
|
+
"exports": {
|
|
215
|
+
"customElement": "ds-calendar",
|
|
216
|
+
"react": "DsCalendar",
|
|
217
|
+
"vue": "DsCalendar",
|
|
218
|
+
"angular": "DsCalendar"
|
|
219
|
+
},
|
|
220
|
+
"consumption": {
|
|
221
|
+
"install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
|
|
222
|
+
"cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
|
|
223
|
+
"customElements": {
|
|
224
|
+
"import": "import '@ds-mo/ui/dist/components/ds-calendar.js';",
|
|
225
|
+
"example": "<ds-calendar></ds-calendar>"
|
|
226
|
+
},
|
|
227
|
+
"react": {
|
|
228
|
+
"import": "import { DsCalendar } from '@ds-mo/ui/react';",
|
|
229
|
+
"example": "<DsCalendar />"
|
|
230
|
+
},
|
|
231
|
+
"vue": {
|
|
232
|
+
"import": "import { DsCalendar } from '@ds-mo/ui/vue';",
|
|
233
|
+
"example": "<DsCalendar />"
|
|
234
|
+
},
|
|
235
|
+
"angular": {
|
|
236
|
+
"import": "import { DsCalendar } from '@ds-mo/ui/angular/ds-calendar';",
|
|
237
|
+
"example": "<ds-calendar></ds-calendar>"
|
|
238
|
+
},
|
|
239
|
+
"peerDependencies": {
|
|
240
|
+
"required": [
|
|
241
|
+
"@ds-mo/tokens ^6.5.1",
|
|
242
|
+
"@ds-mo/icons ^7.0.0"
|
|
243
|
+
],
|
|
244
|
+
"frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"dependencies": [
|
|
249
|
+
"@ds-mo/ui",
|
|
250
|
+
"@ds-mo/tokens"
|
|
251
|
+
],
|
|
252
|
+
"registryDependencies": [
|
|
253
|
+
"button-unfilled",
|
|
254
|
+
"text"
|
|
255
|
+
],
|
|
256
|
+
"files": [
|
|
257
|
+
{
|
|
258
|
+
"path": "src/wc/components/Calendar/Calendar.css",
|
|
259
|
+
"content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: flex;\n min-height: 0;\n flex-direction: column;\n}\n\n.calendar {\n display: flex;\n min-height: 0;\n flex-direction: column;\n min-width: calc(\n (var(--dimension-size-400) * 7) + (var(--dimension-space-050) * 6) +\n (var(--dimension-space-100) * 2)\n );\n padding: var(--ds-calendar-padding, var(--dimension-space-100));\n}\n\n.calendar-heading {\n display: grid;\n grid-template-columns: var(--dimension-size-400) minmax(0, 1fr) var(--dimension-size-400);\n align-items: center;\n text-align: center;\n}\n\n.calendar-nav--previous {\n justify-self: start;\n}\n\n.calendar-nav--next {\n justify-self: end;\n}\n\n.calendar-weekdays,\n.calendar-grid__row {\n display: grid;\n grid-template-columns: repeat(7, minmax(0, 1fr));\n column-gap: var(--dimension-space-050);\n}\n\n.calendar-grid {\n display: flex;\n flex-direction: column;\n row-gap: var(--dimension-space-050);\n}\n\n.calendar-weekdays {\n align-items: center;\n min-height: var(--dimension-size-400);\n margin-block-start: var(--dimension-space-050);\n color: var(--color-foreground-secondary);\n text-align: center;\n}\n\n.calendar-day {\n appearance: none;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n width: 100%;\n height: var(--dimension-size-400);\n padding: 0;\n border: 0;\n border-radius: var(--dimension-radius-025);\n background: transparent;\n color: var(--color-foreground-primary);\n cursor: default;\n}\n\n.calendar-day:disabled {\n cursor: not-allowed;\n}\n\n.calendar-day--outside {\n color: var(--color-foreground-tertiary);\n}\n\n.calendar-day--in-range {\n background: var(--color-background-faint-brand);\n color: var(--color-foreground-primary);\n}\n\n/* In-progress span uses the shared hover overlay; the first day keeps pressed via --surface-open. */\n.calendar-day.ds-interaction-fill.calendar-day--range-preview:not(:disabled)::after {\n background: var(--ds-interaction-hover);\n}\n\n.calendar-day.ds-interaction-fill.calendar-day--range-preview:active:not(:disabled)::after {\n background: var(--ds-interaction-pressed);\n}\n\n.calendar-day--today {\n color: var(--color-foreground-primary);\n}\n\n.calendar-day--range-edge {\n background: var(--color-background-bold-brand);\n color: var(--color-foreground-on-bold-background-primary);\n}\n\n@media (forced-colors: active) {\n .calendar-day--range-edge {\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n }\n}\n",
|
|
260
|
+
"type": "registry:ui"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"path": "src/wc/components/Calendar/Calendar.tsx",
|
|
264
|
+
"content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n Method,\n Prop,\n State,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport {\n dateFilterRangeValue,\n isIsoCalendarDate,\n parseDateFilterValue,\n shiftIsoCalendarDate,\n} from '../../utils';\nimport {\n CALENDAR_WEEKDAYS,\n calendarDays,\n calendarMonth,\n calendarMonthLabel,\n calendarPaintedRange,\n calendarToday,\n shiftCalendarMonth,\n type CalendarDateRange,\n} from './calendar-grid';\n\nexport type CalendarSelectionMode = 'single' | 'range';\n\n@Component({\n tag: 'ds-calendar',\n styleUrl: 'Calendar.css',\n scoped: true,\n})\nexport class Calendar {\n @Element() el!: HTMLElement;\n\n /** Single date emits YYYY-MM-DD. Range emits the shared `range:start/end` string. */\n @Prop() selectionMode: CalendarSelectionMode = 'single';\n @Prop() value: string = '';\n @Prop() min: string | undefined;\n @Prop() max: string | undefined;\n @Prop() isInactive: boolean = false;\n /** Move keyboard focus into the grid after the calendar is rendered. */\n @Prop() autoFocus: boolean = false;\n\n @Event() dsChange!: EventEmitter<string>;\n\n @State() private month = calendarMonth(calendarToday());\n @State() private pendingStart = '';\n @State() private previewEnd = '';\n @State() private heldRange: CalendarDateRange | null = null;\n\n componentWillLoad() {\n this.month = calendarMonth(this.anchorDate());\n }\n\n componentDidLoad() {\n if (this.autoFocus) void this.setFocus();\n }\n\n @Watch('value')\n onValueChange() {\n this.month = calendarMonth(this.anchorDate());\n }\n\n @Method()\n async setFocus() {\n this.el.querySelector<HTMLElement>('.calendar-day[tabindex=\"0\"]')?.focus();\n }\n\n private anchorDate(): string {\n if (this.selectionMode === 'range') {\n const parsed = parseDateFilterValue(this.value);\n if (parsed?.kind === 'range') return parsed.start;\n return calendarToday();\n }\n return isIsoCalendarDate(this.value) ? this.value : calendarToday();\n }\n\n private isDisabled(day: string): boolean {\n if (this.isInactive) return true;\n if (this.min && isIsoCalendarDate(this.min) && day < this.min) return true;\n if (this.max && isIsoCalendarDate(this.max) && day > this.max) return true;\n return false;\n }\n\n private moveMonth = (offset: number) => {\n this.previewEnd = '';\n this.month = shiftCalendarMonth(this.month, offset);\n };\n\n private selectDate = (day: string) => {\n if (this.isDisabled(day)) return;\n this.previewEnd = '';\n if (this.selectionMode === 'single') {\n this.pendingStart = '';\n this.heldRange = null;\n this.dsChange.emit(day);\n return;\n }\n const pending = this.pendingStart;\n if (pending) {\n this.pendingStart = '';\n this.heldRange = null;\n this.dsChange.emit(dateFilterRangeValue(pending, day));\n return;\n }\n const current = parseDateFilterValue(this.value);\n this.heldRange = current?.kind === 'range' ? { start: current.start, end: current.end } : null;\n this.pendingStart = day;\n this.dsChange.emit(dateFilterRangeValue(day, day));\n };\n\n private previewRange = (day: string) => {\n if (!this.pendingStart || this.previewEnd === day) return;\n this.previewEnd = day;\n };\n\n private clearPreview = () => {\n if (!this.previewEnd) return;\n this.previewEnd = '';\n };\n\n private focusDate(day: string) {\n this.previewRange(day);\n const targetMonth = calendarMonth(day);\n if (this.month !== targetMonth) this.month = targetMonth;\n requestAnimationFrame(() => {\n this.el.querySelector<HTMLElement>(`[data-date-option=\"${day}\"]`)?.focus();\n });\n }\n\n private handleDayKeyDown = (event: KeyboardEvent, day: string) => {\n let offset: number | null = null;\n if (event.key === 'ArrowLeft') offset = -1;\n else if (event.key === 'ArrowRight') offset = 1;\n else if (event.key === 'ArrowUp') offset = -7;\n else if (event.key === 'ArrowDown') offset = 7;\n else if (event.key === 'Home') {\n offset = -new Date(`${day}T00:00:00Z`).getUTCDay();\n } else if (event.key === 'End') {\n offset = 6 - new Date(`${day}T00:00:00Z`).getUTCDay();\n } else if (event.key === 'PageUp' || event.key === 'PageDown') {\n const month = shiftCalendarMonth(calendarMonth(day), event.key === 'PageUp' ? -1 : 1);\n const candidate = `${month}-${day.slice(8, 10)}`;\n event.preventDefault();\n this.focusDate(isIsoCalendarDate(candidate) ? candidate : `${month}-01`);\n return;\n }\n if (offset === null) return;\n event.preventDefault();\n this.focusDate(shiftIsoCalendarDate(day, offset));\n };\n\n render() {\n const liveRange = this.selectionMode === 'range' ? parseDateFilterValue(this.value) : null;\n const liveRangeValue = liveRange?.kind === 'range' ? liveRange : null;\n const rangeValue = calendarPaintedRange(this.pendingStart, this.heldRange, liveRangeValue);\n const days = calendarDays(this.month);\n const weeks = Array.from({ length: 6 }, (_, index) => days.slice(index * 7, index * 7 + 7));\n const today = calendarToday();\n const previewStart =\n this.pendingStart && this.previewEnd ? [this.pendingStart, this.previewEnd].sort()[0] : null;\n const previewFinish =\n this.pendingStart && this.previewEnd ? [this.pendingStart, this.previewEnd].sort()[1] : null;\n const selectedSingle =\n this.selectionMode === 'single' && isIsoCalendarDate(this.value) ? this.value : '';\n const preferredFocus =\n this.pendingStart ||\n selectedSingle ||\n rangeValue?.end ||\n (this.month === today.slice(0, 7) ? today : '');\n const focusDate = days.some(day => day.value === preferredFocus)\n ? preferredFocus\n : (days.find(day => day.inMonth)?.value ?? days[0]?.value);\n\n return (\n <Host>\n <div class=\"calendar\">\n <div class=\"calendar-heading ds-control--md\">\n <ds-button-unfilled\n class=\"calendar-nav calendar-nav--previous\"\n variant=\"icon\"\n icon=\"ChevronLeft\"\n size=\"md\"\n hasBorder={false}\n isInactive={this.isInactive}\n ariaLabel=\"Previous month\"\n onDsClick={() => this.moveMonth(-1)}\n />\n <ds-text as=\"span\" variant=\"text-body-medium\" emphasis color=\"primary\">\n {calendarMonthLabel(this.month)}\n </ds-text>\n <ds-button-unfilled\n class=\"calendar-nav calendar-nav--next\"\n variant=\"icon\"\n icon=\"ChevronRight\"\n size=\"md\"\n hasBorder={false}\n isInactive={this.isInactive}\n ariaLabel=\"Next month\"\n onDsClick={() => this.moveMonth(1)}\n />\n </div>\n <div class=\"calendar-weekdays\" aria-hidden=\"true\">\n {CALENDAR_WEEKDAYS.map(day => (\n <ds-text as=\"span\" variant=\"text-body-small\" color=\"secondary\">\n {day}\n </ds-text>\n ))}\n </div>\n <div\n class=\"calendar-grid\"\n role=\"grid\"\n aria-label={calendarMonthLabel(this.month)}\n onMouseLeave={this.clearPreview}\n >\n {weeks.map((week, weekIndex) => (\n <div class=\"calendar-grid__row\" role=\"row\" key={`week-${weekIndex}`}>\n {week.map(day => {\n const selectedInRange = Boolean(\n liveRangeValue &&\n day.value >= liveRangeValue.start &&\n day.value <= liveRangeValue.end\n );\n const paintedInRange = Boolean(\n rangeValue && day.value >= rangeValue.start && day.value <= rangeValue.end\n );\n const pendingStartDay =\n this.selectionMode === 'range' &&\n Boolean(this.pendingStart) &&\n day.value === this.pendingStart;\n const previewInRange = Boolean(\n this.pendingStart &&\n previewStart &&\n previewFinish &&\n day.value >= previewStart &&\n day.value <= previewFinish &&\n day.value !== this.pendingStart\n );\n const inRange = this.selectionMode === 'range' && paintedInRange;\n const rangeEdge = Boolean(\n this.selectionMode === 'range' &&\n rangeValue &&\n (day.value === rangeValue.start || day.value === rangeValue.end)\n );\n const selected = this.selectionMode === 'single' && day.value === selectedSingle;\n const textColor = rangeEdge\n ? 'on-bold'\n : selected ||\n inRange ||\n pendingStartDay ||\n previewInRange ||\n day.value === today\n ? 'primary'\n : day.inMonth\n ? 'secondary'\n : 'tertiary';\n return (\n <button\n type=\"button\"\n role=\"gridcell\"\n data-date-option={day.value}\n class={{\n 'calendar-day': true,\n 'calendar-day--outside': !day.inMonth,\n 'calendar-day--today': day.value === today,\n 'calendar-day--in-range': inRange,\n 'calendar-day--range-preview': previewInRange,\n 'calendar-day--range-edge': rangeEdge,\n 'calendar-day--selected': selected,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--on-bold': rangeEdge,\n 'ds-interaction-fill--surface-open':\n pendingStartDay && !this.isDisabled(day.value),\n 'ds-interaction-fill--selected': selected && !this.isDisabled(day.value),\n }}\n disabled={this.isDisabled(day.value)}\n aria-label={day.label}\n aria-selected={selectedInRange || selected ? 'true' : 'false'}\n tabIndex={day.value === focusDate ? 0 : -1}\n onMouseEnter={() => this.previewRange(day.value)}\n onFocus={() => this.previewRange(day.value)}\n onClick={() => this.selectDate(day.value)}\n onKeyDown={event => this.handleDayKeyDown(event, day.value)}\n >\n <ds-text\n class=\"ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color={textColor}\n emphasis={day.value === today}\n >\n {day.day}\n </ds-text>\n </button>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
265
|
+
"type": "registry:ui"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"path": "src/wc/components/Calendar/calendar-grid.ts",
|
|
269
|
+
"content": "import { isIsoCalendarDate, shiftIsoCalendarDate } from '../../utils';\n\nexport interface CalendarDateRange {\n start: string;\n end: string;\n}\n\nexport interface CalendarDay {\n value: string;\n day: number;\n inMonth: boolean;\n label: string;\n}\n\nexport const CALENDAR_WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n\nconst MONTH_FORMATTER = new Intl.DateTimeFormat('en-US', {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n});\nconst DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n timeZone: 'UTC',\n});\n\nexport function calendarToday(): string {\n const today = new Date();\n const year = today.getFullYear();\n const month = String(today.getMonth() + 1).padStart(2, '0');\n const day = String(today.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\nexport function calendarMonth(value: string): string {\n return isIsoCalendarDate(value) ? value.slice(0, 7) : calendarToday().slice(0, 7);\n}\n\nexport function shiftCalendarMonth(value: string, offset: number): string {\n const [year, month] = value.split('-').map(Number);\n const date = new Date(Date.UTC(year, month - 1 + offset, 1));\n return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;\n}\n\nexport function calendarMonthLabel(value: string): string {\n const [year, month] = value.split('-').map(Number);\n return MONTH_FORMATTER.format(new Date(Date.UTC(year, month - 1, 1)));\n}\n\nexport function calendarDays(value: string): CalendarDay[] {\n const [year, month] = value.split('-').map(Number);\n const firstOfMonth = `${year}-${String(month).padStart(2, '0')}-01`;\n const firstWeekday = new Date(`${firstOfMonth}T00:00:00Z`).getUTCDay();\n const gridStart = shiftIsoCalendarDate(firstOfMonth, -firstWeekday);\n return Array.from({ length: 42 }, (_, index) => {\n const dateValue = shiftIsoCalendarDate(gridStart, index);\n const date = new Date(`${dateValue}T00:00:00Z`);\n return {\n value: dateValue,\n day: date.getUTCDate(),\n inMonth: dateValue.startsWith(`${value}-`),\n label: DATE_FORMATTER.format(date),\n };\n });\n}\n\n/** Keep the previous committed range visible while a replacement range is being chosen. */\nexport function calendarPaintedRange(\n pendingStart: string,\n heldRange: CalendarDateRange | null,\n valueRange: CalendarDateRange | null\n): CalendarDateRange | null {\n return pendingStart ? heldRange : valueRange;\n}\n",
|
|
270
|
+
"type": "registry:ui"
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}
|
|
@@ -51,10 +51,12 @@
|
|
|
51
51
|
"The native link owns hover, pressed, and focus-visible states across the complete card in navigation-only mode.",
|
|
52
52
|
"In content mode, the native link owns those interaction states only across the header while the body remains application-owned.",
|
|
53
53
|
"An empty or unsafe href renders non-interactive card chrome without a false keyboard target.",
|
|
54
|
+
"Copy is always stacked, so a missing description keeps the heading at the start of the column instead of centering it in the header.",
|
|
54
55
|
"The application owns destination loading, authorization, not-found, and recovery states after navigation."
|
|
55
56
|
],
|
|
56
57
|
"responsiveBehavior": [
|
|
57
58
|
"Choose sm, md, or lg from the parent layout; the surface never exceeds its parent width.",
|
|
59
|
+
"The heading stays at the start of the stacked copy column whether or not supporting copy is present, so mixed destination lists keep titles aligned. The chevron stays vertically centered in the header.",
|
|
58
60
|
"Supporting copy wraps while the chevron remains visible at the trailing edge.",
|
|
59
61
|
"The parent owns wrapping, columns, stacking, and breakpoint changes between cards."
|
|
60
62
|
],
|
|
@@ -243,12 +245,12 @@
|
|
|
243
245
|
"files": [
|
|
244
246
|
{
|
|
245
247
|
"path": "src/wc/components/CardNavigation/CardNavigation.css",
|
|
246
|
-
"content": "@import '../../utils/radius.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n position: relative;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--_card-navigation-width);\n max-width: 100%;\n overflow: hidden;\n border: none;\n border-radius: var(--ds-radius-card);\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-shadow-elevated-sm);\n}\n\n:host(.card-navigation--content) {\n min-height: var(--_card-navigation-min-height);\n}\n\n:host::after {\n content: '';\n position: absolute;\n inset: 0;\n z-index: 4;\n border-radius: inherit;\n box-shadow: var(--effect-highlight-elevated-sm);\n pointer-events: none;\n}\n\n.card-navigation__target {\n color: inherit;\n text-decoration: none;\n cursor: pointer;\n}\n\n.card-navigation__header {\n flex: 0 0 auto;\n border-radius: inherit;\n}\n\n:host(.card-navigation--content) .card-navigation__header {\n border-radius: var(--ds-radius-card) var(--ds-radius-card) 0 0;\n --ds-interaction-divider-width: var(--dimension-stroke-width-012);\n --ds-interaction-divider-color: var(--color-border-tertiary);\n}\n\n.card-navigation__copy {\n min-height: var(--dimension-size-400);\n justify-content:
|
|
248
|
+
"content": "@import '../../utils/radius.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n position: relative;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--_card-navigation-width);\n max-width: 100%;\n overflow: hidden;\n border: none;\n border-radius: var(--ds-radius-card);\n background-color: var(--color-background-primary);\n box-shadow: var(--effect-shadow-elevated-sm);\n}\n\n:host(.card-navigation--content) {\n min-height: var(--_card-navigation-min-height);\n}\n\n:host::after {\n content: '';\n position: absolute;\n inset: 0;\n z-index: 4;\n border-radius: inherit;\n box-shadow: var(--effect-highlight-elevated-sm);\n pointer-events: none;\n}\n\n.card-navigation__target {\n color: inherit;\n text-decoration: none;\n cursor: pointer;\n}\n\n.card-navigation__header {\n flex: 0 0 auto;\n align-items: flex-start;\n border-radius: inherit;\n}\n\n:host(.card-navigation--content) .card-navigation__header {\n border-radius: var(--ds-radius-card) var(--ds-radius-card) 0 0;\n --ds-interaction-divider-width: var(--dimension-stroke-width-012);\n --ds-interaction-divider-color: var(--color-border-tertiary);\n}\n\n.card-navigation__copy {\n min-height: var(--dimension-size-400);\n justify-content: flex-start;\n}\n\nds-text.card-navigation__title,\nds-text.card-navigation__description {\n overflow-wrap: anywhere;\n}\n\n.card-navigation__chevron {\n align-self: center;\n justify-content: center;\n width: var(--dimension-size-400);\n color: var(--color-foreground-secondary);\n}\n\n.card-navigation__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-height: 0;\n box-sizing: border-box;\n background-color: var(--color-background-primary);\n border-radius: 0 0 var(--ds-radius-card) var(--ds-radius-card);\n}\n\n@media (forced-colors: active) {\n :host {\n box-shadow: none;\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n }\n\n :host::after {\n box-shadow: none;\n }\n}\n",
|
|
247
249
|
"type": "registry:ui"
|
|
248
250
|
},
|
|
249
251
|
{
|
|
250
252
|
"path": "src/wc/components/CardNavigation/CardNavigation.tsx",
|
|
251
|
-
"content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport { resolveSafeUrl } from '../../utils';\n\nexport type CardNavigationWidth = 'sm' | 'md' | 'lg';\nexport type CardNavigationVariant = 'navigation-only' | 'content';\n\nexport interface CardNavigationDetail {\n href: string;\n originalEvent: MouseEvent;\n}\n\nconst CARD_WIDTH_VARS: Record<CardNavigationWidth, string> = {\n sm: 'var(--dimension-card-width-sm)',\n md: 'var(--dimension-card-width-md)',\n lg: 'var(--dimension-card-width-lg)',\n};\n\nconst CARD_HEIGHT_VARS: Record<CardNavigationWidth, string> = {\n sm: 'var(--dimension-card-height-sm)',\n md: 'var(--dimension-card-height-md)',\n lg: 'var(--dimension-card-height-lg)',\n};\n\n@Component({\n tag: 'ds-card-navigation',\n styleUrl: 'CardNavigation.css',\n scoped: true,\n})\nexport class CardNavigation {\n /** Destination for the card's native link. */\n @Prop() href!: string;\n\n /** Section or destination heading shown in the card header. */\n @Prop() heading!: string;\n\n /** Optional supporting copy shown below the heading. */\n @Prop() description: string | undefined;\n\n /**\n * `navigation-only` makes the complete card the link. `content` keeps the\n * header as the link and exposes a non-interactive body slot below it.\n */\n @Prop() variant: CardNavigationVariant = 'navigation-only';\n\n /** Card width token (`sm` / `md` / `lg`). */\n @Prop() cardWidth: CardNavigationWidth = 'md';\n\n /**\n * Emits before native navigation. Prevent this event to take over routing;\n * the component will then prevent the original link navigation.\n */\n @Event({ cancelable: true }) dsNavigate!: EventEmitter<CardNavigationDetail>;\n\n private handleNavigation(href: string, originalEvent: MouseEvent) {\n const navigateEvent = this.dsNavigate.emit({ href, originalEvent });\n if (navigateEvent.defaultPrevented) originalEvent.preventDefault();\n }\n\n private renderHeader(href: string | undefined) {\n const Target = href ? 'a' : 'div';\n const description = this.description?.trim();\n\n return (\n <Target\n class={{\n 'card-navigation__target': true,\n 'card-navigation__header': true,\n 'ds-chrome-header': true,\n 'ds-interaction-fill': !!href,\n 'ds-focus-ring-inset': !!href,\n }}\n href={href}\n onClick={href ? (event: MouseEvent) => this.handleNavigation(href, event) : undefined}\n >\n <div\n class={{\n 'card-navigation__copy': true,\n 'ds-chrome-header__copy': true,\n 'ds-chrome-header__copy--stacked':
|
|
253
|
+
"content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport { resolveSafeUrl } from '../../utils';\n\nexport type CardNavigationWidth = 'sm' | 'md' | 'lg';\nexport type CardNavigationVariant = 'navigation-only' | 'content';\n\nexport interface CardNavigationDetail {\n href: string;\n originalEvent: MouseEvent;\n}\n\nconst CARD_WIDTH_VARS: Record<CardNavigationWidth, string> = {\n sm: 'var(--dimension-card-width-sm)',\n md: 'var(--dimension-card-width-md)',\n lg: 'var(--dimension-card-width-lg)',\n};\n\nconst CARD_HEIGHT_VARS: Record<CardNavigationWidth, string> = {\n sm: 'var(--dimension-card-height-sm)',\n md: 'var(--dimension-card-height-md)',\n lg: 'var(--dimension-card-height-lg)',\n};\n\n@Component({\n tag: 'ds-card-navigation',\n styleUrl: 'CardNavigation.css',\n scoped: true,\n})\nexport class CardNavigation {\n /** Destination for the card's native link. */\n @Prop() href!: string;\n\n /** Section or destination heading shown in the card header. */\n @Prop() heading!: string;\n\n /** Optional supporting copy shown below the heading. */\n @Prop() description: string | undefined;\n\n /**\n * `navigation-only` makes the complete card the link. `content` keeps the\n * header as the link and exposes a non-interactive body slot below it.\n */\n @Prop() variant: CardNavigationVariant = 'navigation-only';\n\n /** Card width token (`sm` / `md` / `lg`). */\n @Prop() cardWidth: CardNavigationWidth = 'md';\n\n /**\n * Emits before native navigation. Prevent this event to take over routing;\n * the component will then prevent the original link navigation.\n */\n @Event({ cancelable: true }) dsNavigate!: EventEmitter<CardNavigationDetail>;\n\n private handleNavigation(href: string, originalEvent: MouseEvent) {\n const navigateEvent = this.dsNavigate.emit({ href, originalEvent });\n if (navigateEvent.defaultPrevented) originalEvent.preventDefault();\n }\n\n private renderHeader(href: string | undefined) {\n const Target = href ? 'a' : 'div';\n const description = this.description?.trim();\n\n return (\n <Target\n class={{\n 'card-navigation__target': true,\n 'card-navigation__header': true,\n 'ds-chrome-header': true,\n 'ds-interaction-fill': !!href,\n 'ds-focus-ring-inset': !!href,\n }}\n href={href}\n onClick={href ? (event: MouseEvent) => this.handleNavigation(href, event) : undefined}\n >\n <div\n class={{\n 'card-navigation__copy': true,\n 'ds-chrome-header__copy': true,\n 'ds-chrome-header__copy--stacked': true,\n 'ds-control--md': true,\n 'ds-interaction-fill__content': true,\n }}\n >\n <ds-text\n class=\"card-navigation__title ds-chrome-header__heading\"\n variant=\"text-title-small\"\n emphasis\n color=\"primary\"\n as=\"h2\"\n >\n {this.heading}\n </ds-text>\n {description ? (\n <ds-text\n class=\"card-navigation__description ds-chrome-header__description\"\n variant=\"text-body-small\"\n color=\"secondary\"\n as=\"span\"\n >\n {description}\n </ds-text>\n ) : null}\n </div>\n <span class=\"card-navigation__chevron ds-chrome-header__trailing ds-interaction-fill__content\">\n <ds-icon name=\"ChevronRight\" size=\"md\" color=\"inherit\" aria-hidden=\"true\" />\n </span>\n </Target>\n );\n }\n\n render() {\n const href = resolveSafeUrl(this.href);\n const hasContent = this.variant === 'content';\n\n return (\n <Host\n class={{\n 'card-navigation': true,\n 'card-navigation--navigation-only': !hasContent,\n 'card-navigation--content': hasContent,\n }}\n style={{\n '--_card-navigation-width': CARD_WIDTH_VARS[this.cardWidth],\n '--_card-navigation-min-height': CARD_HEIGHT_VARS[this.cardWidth],\n }}\n >\n {this.renderHeader(href)}\n {hasContent ? (\n <div class=\"card-navigation__body\">\n <slot />\n </div>\n ) : null}\n </Host>\n );\n }\n}\n",
|
|
252
254
|
"type": "registry:ui"
|
|
253
255
|
}
|
|
254
256
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "field",
|
|
4
4
|
"title": "Field",
|
|
5
|
-
"description": "Accessible form-field composition that gives one Input, Textarea, or Select
|
|
5
|
+
"description": "Accessible form-field composition that gives one Input, InputDate, InputTime, Textarea, or Select an optional visible label, optional guidance, shared error presentation, stable IDs, and observable interaction state.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "general",
|
|
15
15
|
"status": "stable",
|
|
16
|
-
"summary": "Accessible form-field composition that gives one Input, Textarea, or Select
|
|
16
|
+
"summary": "Accessible form-field composition that gives one Input, InputDate, InputTime, Textarea, or Select an optional visible label, optional guidance, shared error presentation, stable IDs, and observable interaction state.",
|
|
17
17
|
"useWhen": [
|
|
18
|
-
"An Input, Textarea, or Select needs a persistent visible label in a form.",
|
|
18
|
+
"An Input, InputDate, InputTime, Textarea, or Select needs a persistent visible label in a form.",
|
|
19
|
+
"A nearby heading already names the field, and the control still needs Field's error association and message rhythm.",
|
|
19
20
|
"Help text or an error message must be programmatically associated with one control.",
|
|
20
21
|
"A form layout needs one consistent label-control-message rhythm."
|
|
21
22
|
],
|
|
@@ -33,19 +34,23 @@
|
|
|
33
34
|
],
|
|
34
35
|
"commonlyComposedWith": [
|
|
35
36
|
"component:ds-input",
|
|
37
|
+
"component:ds-input-date",
|
|
38
|
+
"component:ds-input-time",
|
|
36
39
|
"component:ds-textarea",
|
|
37
40
|
"component:ds-select",
|
|
38
41
|
"component:ds-text"
|
|
39
42
|
],
|
|
40
43
|
"accessibility": [
|
|
41
|
-
"Slot exactly one form control. Field assigns its stable control ID and connects
|
|
44
|
+
"Slot exactly one form control. When a visible label is provided, Field assigns its stable control ID and connects that label automatically.",
|
|
45
|
+
"Omit the visible label when a nearby heading already names the field. Keep a programmatic name on the slotted control, such as aria-label, and do not set aria-labelledby to an empty value.",
|
|
42
46
|
"The visible description or error ID is merged with any authored aria-describedby references; a rendered error replaces the description.",
|
|
43
47
|
"The error prop forwards aria-invalid and supported design-system control error styling; put visible error text on Field, not again on the child control.",
|
|
44
48
|
"Clicking the label focuses the slotted design-system control without opening a Select popup.",
|
|
45
49
|
"Do not use placeholder text as the only label. Keep label text concise and put format or consequence guidance in description."
|
|
46
50
|
],
|
|
47
51
|
"states": [
|
|
48
|
-
"
|
|
52
|
+
"The visible label is optional. Omit it when a nearby heading already names the field; Field still associates description and error text with the slotted control.",
|
|
53
|
+
"Label, control, description, and error content follow the shared 4px vertical field flow without changing the slotted control's width or semantics. When the slotted control is borderless, description and error copy inset to that control's text origin instead of the resting bordered edge.",
|
|
49
54
|
"Field exposes data-focused, data-filled, data-dirty, data-touched, data-invalid, data-disabled, and data-required hooks while the slotted control remains the source of value and interaction behavior.",
|
|
50
55
|
"A rendered error replaces the description; preserve essential guidance in the error message so users retain what they need to correct the field.",
|
|
51
56
|
"Field owns validation presentation but not validation timing; the form decides when to set error and errorMessage.",
|
|
@@ -61,7 +66,7 @@
|
|
|
61
66
|
"Assign child component values and option arrays through their normal JavaScript properties."
|
|
62
67
|
],
|
|
63
68
|
"react": [
|
|
64
|
-
"Compose one DsInput, DsTextarea, or DsSelect child and control validation timing in the form owner."
|
|
69
|
+
"Compose one DsInput, DsInputDate, DsInputTime, DsTextarea, or DsSelect child and control validation timing in the form owner."
|
|
65
70
|
],
|
|
66
71
|
"angular": [
|
|
67
72
|
"Compose the generated Field adapter around the control adapter and its matching Angular value accessor."
|
|
@@ -115,11 +120,11 @@
|
|
|
115
120
|
},
|
|
116
121
|
"label": {
|
|
117
122
|
"type": "string",
|
|
118
|
-
"resolvedType": "string",
|
|
123
|
+
"resolvedType": "string | undefined",
|
|
119
124
|
"attribute": "label",
|
|
120
|
-
"required":
|
|
125
|
+
"required": false,
|
|
121
126
|
"mutable": false,
|
|
122
|
-
"description": "
|
|
127
|
+
"description": "Visible label for the slotted control. Omit when a nearby heading already names the field."
|
|
123
128
|
}
|
|
124
129
|
},
|
|
125
130
|
"events": [],
|
|
@@ -162,11 +167,11 @@
|
|
|
162
167
|
},
|
|
163
168
|
"label": {
|
|
164
169
|
"type": "string",
|
|
165
|
-
"resolvedType": "string",
|
|
170
|
+
"resolvedType": "string | undefined",
|
|
166
171
|
"attribute": "label",
|
|
167
|
-
"required":
|
|
172
|
+
"required": false,
|
|
168
173
|
"mutable": false,
|
|
169
|
-
"description": "
|
|
174
|
+
"description": "Visible label for the slotted control. Omit when a nearby heading already names the field."
|
|
170
175
|
}
|
|
171
176
|
},
|
|
172
177
|
"events": [],
|
|
@@ -216,12 +221,12 @@
|
|
|
216
221
|
"files": [
|
|
217
222
|
{
|
|
218
223
|
"path": "src/wc/components/Field/Field.css",
|
|
219
|
-
"content": "@import '../../utils/field-stack.css';\n\n:host {\n display: block;\n}\n\n.field__control {\n display: flex;\n min-width: 0;\n}\n\n.field__control > * {\n width: 100%;\n}\n",
|
|
224
|
+
"content": "@import '../../utils/control-density.css';\n@import '../../utils/field-stack.css';\n\n:host {\n display: block;\n}\n\n.field__control {\n display: flex;\n min-width: 0;\n}\n\n.field__control > * {\n width: 100%;\n}\n",
|
|
220
225
|
"type": "registry:ui"
|
|
221
226
|
},
|
|
222
227
|
{
|
|
223
228
|
"path": "src/wc/components/Field/Field.tsx",
|
|
224
|
-
"content": "import { Component, Element, Prop, State, Watch, h, Host } from '@stencil/core';\n\ninterface FieldControl extends HTMLElement {\n value?: string | string[];\n inputId?: string;\n error?: boolean;\n disabled?: boolean;\n isInactive?: boolean;\n required?: boolean;\n setFocus?: () => Promise<void> | void;\n}\n\ninterface AuthoredAria {\n labelledby: string[];\n describedby: string[];\n}\n\nlet fieldCounter = 0;\n\nconst tokens = (value: string | null | undefined): string[] =>\n value?.split(/\\s+/).filter(Boolean) ?? [];\n\nconst uniqueTokens = (...groups: string[][]): string[] => [...new Set(groups.flat())];\n\n@Component({\n tag: 'ds-field',\n styleUrl: 'Field.css',\n scoped: true,\n})\nexport class Field {\n @Element() el!: HTMLElement;\n\n /** Persistent visible label for the single slotted control. */\n @Prop() label!: string;\n /** Explicit ID for the slotted control; generated when omitted. */\n @Prop() fieldId: string | undefined;\n /** Optional guidance associated with the slotted control while no visible error is shown. */\n @Prop() description: string | undefined;\n /** Invalid visual and accessible state forwarded to supported ds controls. */\n @Prop() error: boolean = false;\n /** Visible error associated with the slotted control while error is true. */\n @Prop() errorMessage: string | undefined;\n\n @State() private focused = false;\n @State() private filled = false;\n @State() private dirty = false;\n @State() private touched = false;\n @State() private controlDisabled = false;\n @State() private controlRequired = false;\n /** Bumped to let Stencil relocate a control added after the initial render. */\n @State() private slotRevision = 0;\n\n private readonly generatedId = `ds-field-${++fieldCounter}`;\n private controlContainer?: HTMLDivElement;\n private control?: FieldControl;\n private initialValue = '';\n private authoredAria = new WeakMap<FieldControl, AuthoredAria>();\n private childObserver?: MutationObserver;\n private didLoad = false;\n\n connectedCallback() {\n if (this.didLoad) this.observeLateControls();\n }\n\n disconnectedCallback() {\n this.childObserver?.disconnect();\n this.childObserver = undefined;\n }\n\n componentDidLoad() {\n this.syncControl();\n this.didLoad = true;\n this.observeLateControls();\n requestAnimationFrame(() => this.syncControl());\n }\n\n componentDidRender() {\n this.syncControl();\n }\n\n @Watch('fieldId')\n @Watch('description')\n @Watch('error')\n @Watch('errorMessage')\n onFieldContractChange() {\n this.syncControl();\n }\n\n private get controlId(): string {\n return this.fieldId || this.generatedId;\n }\n\n private get labelId(): string {\n return `${this.controlId}-label`;\n }\n\n private get descriptionId(): string {\n return `${this.controlId}-description`;\n }\n\n private get errorId(): string {\n return `${this.controlId}-error`;\n }\n\n private get renderedDescription(): boolean {\n return Boolean(this.description) && !this.renderedError;\n }\n\n private get renderedError(): boolean {\n return this.error && Boolean(this.errorMessage);\n }\n\n private readValue(control: FieldControl): string {\n const value = control.value;\n return Array.isArray(value) ? value.join('\\u001f') : String(value ?? '');\n }\n\n private updateControlAttribute(control: FieldControl, name: string, value: string | undefined) {\n if (value === undefined) {\n if (control.hasAttribute(name)) control.removeAttribute(name);\n return;\n }\n if (control.getAttribute(name) !== value) control.setAttribute(name, value);\n }\n\n private findControl(): FieldControl | undefined {\n return this.controlContainer?.querySelector<FieldControl>(':scope > :not(slot)') ?? undefined;\n }\n\n private observeLateControls() {\n if (this.childObserver || typeof MutationObserver === 'undefined') return;\n this.childObserver = new MutationObserver(records => {\n const addedControl = records.some(record =>\n Array.from(record.addedNodes).some(node => node.nodeType === 1)\n );\n if (addedControl) this.slotRevision++;\n });\n this.childObserver.observe(this.el, { childList: true });\n }\n\n private syncControl = () => {\n const control = this.findControl();\n if (!control) return;\n\n if (control !== this.control) {\n this.control = control;\n this.initialValue = this.readValue(control);\n this.authoredAria.set(control, {\n labelledby: tokens(control.getAttribute('aria-labelledby')),\n describedby: tokens(control.getAttribute('aria-describedby')),\n });\n }\n\n const isDsControl = control.tagName.startsWith('DS-');\n if (isDsControl && 'inputId' in control) control.inputId = this.controlId;\n else this.updateControlAttribute(control, 'id', this.controlId);\n\n const authored = this.authoredAria.get(control) ?? { labelledby: [], describedby: [] };\n const labelledby = uniqueTokens(authored.labelledby, [this.labelId]).join(' ');\n const describedby = uniqueTokens(\n authored.describedby,\n this.renderedDescription ? [this.descriptionId] : [],\n this.renderedError ? [this.errorId] : []\n ).join(' ');\n\n this.updateControlAttribute(control, 'aria-labelledby', labelledby);\n this.updateControlAttribute(control, 'aria-describedby', describedby || undefined);\n this.updateControlAttribute(control, 'aria-invalid', this.error ? 'true' : undefined);\n\n if (isDsControl && 'error' in control && control.error !== this.error)\n control.error = this.error;\n\n const value = this.readValue(control);\n this.filled = value.length > 0;\n this.dirty = value !== this.initialValue;\n this.controlDisabled = Boolean(\n control.disabled || control.isInactive || control.hasAttribute('disabled')\n );\n this.controlRequired = Boolean(control.required || control.hasAttribute('required'));\n };\n\n private handleLabelClick = (event: MouseEvent) => {\n if (!this.control?.tagName.startsWith('DS-')) return;\n event.preventDefault();\n this.control.setFocus?.();\n };\n\n private handleFocusIn = () => {\n this.focused = true;\n };\n\n private handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && this.el.contains(event.relatedTarget)) return;\n this.focused = false;\n this.touched = true;\n };\n\n private handleValueChange = () => {\n this.syncControl();\n };\n\n render() {\n return (\n <Host\n data-focused={this.focused ? '' : undefined}\n data-filled={this.filled ? '' : undefined}\n data-dirty={this.dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n data-invalid={this.error ? '' : undefined}\n data-disabled={this.controlDisabled ? '' : undefined}\n data-required={this.controlRequired ? '' : undefined}\n onFocusin={this.handleFocusIn}\n onFocusout={this.handleFocusOut}\n onInput={this.handleValueChange}\n onChange={this.handleValueChange}\n onDsChange={this.handleValueChange}\n >\n <div class=\"field ds-field-stack\">\n <ds-text\n class=\"field__label\"\n as=\"label\"\n variant=\"text-body-small\"\n color=\"primary\"\n emphasis\n for={this.controlId}\n textId={this.labelId}\n onClick={this.handleLabelClick}\n >\n {this.label}\n </ds-text>\n <div class=\"field__control\" ref={el => (this.controlContainer = el)}>\n <slot onSlotchange={this.syncControl} />\n </div>\n {this.renderedDescription && (\n <ds-text\n class=\"field__description\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"secondary\"\n textId={this.descriptionId}\n >\n {this.description}\n </ds-text>\n )}\n {this.renderedError && (\n <ds-text\n class=\"field__error\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </div>\n </Host>\n );\n }\n}\n",
|
|
229
|
+
"content": "import { Component, Element, Prop, State, Watch, h, Host } from '@stencil/core';\n\ninterface FieldControl extends HTMLElement {\n value?: string | string[];\n inputId?: string;\n error?: boolean;\n disabled?: boolean;\n isInactive?: boolean;\n required?: boolean;\n hasBorder?: boolean;\n size?: string;\n setFocus?: () => Promise<void> | void;\n}\n\ninterface AuthoredAria {\n labelledby: string[];\n describedby: string[];\n}\n\nlet fieldCounter = 0;\n\nconst tokens = (value: string | null | undefined): string[] =>\n value?.split(/\\s+/).filter(Boolean) ?? [];\n\nconst uniqueTokens = (...groups: string[][]): string[] => [...new Set(groups.flat())];\n\nconst CONTROL_SIZES = ['lg', 'md', 'sm', 'xs'] as const;\ntype FieldControlSize = (typeof CONTROL_SIZES)[number];\n\nconst readControlSize = (control: FieldControl): FieldControlSize =>\n CONTROL_SIZES.includes(control.size as FieldControlSize)\n ? (control.size as FieldControlSize)\n : 'md';\n\n@Component({\n tag: 'ds-field',\n styleUrl: 'Field.css',\n scoped: true,\n})\nexport class Field {\n @Element() el!: HTMLElement;\n\n /** Visible label for the slotted control. Omit when a nearby heading already names the field. */\n @Prop() label?: string;\n /** Explicit ID for the slotted control; generated when omitted. */\n @Prop() fieldId: string | undefined;\n /** Optional guidance associated with the slotted control while no visible error is shown. */\n @Prop() description: string | undefined;\n /** Invalid visual and accessible state forwarded to supported ds controls. */\n @Prop() error: boolean = false;\n /** Visible error associated with the slotted control while error is true. */\n @Prop() errorMessage: string | undefined;\n\n @State() private focused = false;\n @State() private filled = false;\n @State() private dirty = false;\n @State() private touched = false;\n @State() private controlDisabled = false;\n @State() private controlRequired = false;\n @State() private controlBorderless = false;\n @State() private controlSize: 'lg' | 'md' | 'sm' | 'xs' = 'md';\n /** Bumped to let Stencil relocate a control added after the initial render. */\n @State() private slotRevision = 0;\n\n private readonly generatedId = `ds-field-${++fieldCounter}`;\n private controlContainer?: HTMLDivElement;\n private control?: FieldControl;\n private initialValue = '';\n private authoredAria = new WeakMap<FieldControl, AuthoredAria>();\n private childObserver?: MutationObserver;\n private controlAttrObserver?: MutationObserver;\n private didLoad = false;\n\n connectedCallback() {\n if (this.didLoad) this.observeLateControls();\n }\n\n disconnectedCallback() {\n this.childObserver?.disconnect();\n this.childObserver = undefined;\n this.controlAttrObserver?.disconnect();\n this.controlAttrObserver = undefined;\n }\n\n componentDidLoad() {\n this.syncControl();\n this.didLoad = true;\n this.observeLateControls();\n requestAnimationFrame(() => this.syncControl());\n }\n\n componentDidRender() {\n this.syncControl();\n }\n\n @Watch('fieldId')\n @Watch('label')\n @Watch('description')\n @Watch('error')\n @Watch('errorMessage')\n onFieldContractChange() {\n this.syncControl();\n }\n\n private get hasLabel(): boolean {\n return Boolean(this.label?.trim());\n }\n\n private get controlId(): string {\n return this.fieldId || this.generatedId;\n }\n\n private get labelId(): string {\n return `${this.controlId}-label`;\n }\n\n private get descriptionId(): string {\n return `${this.controlId}-description`;\n }\n\n private get errorId(): string {\n return `${this.controlId}-error`;\n }\n\n private get renderedDescription(): boolean {\n return Boolean(this.description) && !this.renderedError;\n }\n\n private get renderedError(): boolean {\n return this.error && Boolean(this.errorMessage);\n }\n\n private readValue(control: FieldControl): string {\n const value = control.value;\n return Array.isArray(value) ? value.join('\\u001f') : String(value ?? '');\n }\n\n private updateControlAttribute(control: FieldControl, name: string, value: string | undefined) {\n if (value === undefined) {\n if (control.hasAttribute(name)) control.removeAttribute(name);\n return;\n }\n if (control.getAttribute(name) !== value) control.setAttribute(name, value);\n }\n\n private findControl(): FieldControl | undefined {\n return this.controlContainer?.querySelector<FieldControl>(':scope > :not(slot)') ?? undefined;\n }\n\n private observeLateControls() {\n if (this.childObserver || typeof MutationObserver === 'undefined') return;\n this.childObserver = new MutationObserver(records => {\n const addedControl = records.some(record =>\n Array.from(record.addedNodes).some(node => node.nodeType === 1)\n );\n if (addedControl) this.slotRevision++;\n });\n this.childObserver.observe(this.el, { childList: true });\n }\n\n private syncControl = () => {\n const control = this.findControl();\n if (!control) return;\n\n if (control !== this.control) {\n this.control = control;\n this.initialValue = this.readValue(control);\n this.authoredAria.set(control, {\n labelledby: tokens(control.getAttribute('aria-labelledby')),\n describedby: tokens(control.getAttribute('aria-describedby')),\n });\n this.observeControlAttributes(control);\n }\n\n const isDsControl = control.tagName.startsWith('DS-');\n if (isDsControl && 'inputId' in control) control.inputId = this.controlId;\n else this.updateControlAttribute(control, 'id', this.controlId);\n\n const authored = this.authoredAria.get(control) ?? { labelledby: [], describedby: [] };\n const labelledby = uniqueTokens(authored.labelledby, this.hasLabel ? [this.labelId] : []).join(\n ' '\n );\n const describedby = uniqueTokens(\n authored.describedby,\n this.renderedDescription ? [this.descriptionId] : [],\n this.renderedError ? [this.errorId] : []\n ).join(' ');\n\n this.updateControlAttribute(control, 'aria-labelledby', labelledby || undefined);\n this.updateControlAttribute(control, 'aria-describedby', describedby || undefined);\n this.updateControlAttribute(control, 'aria-invalid', this.error ? 'true' : undefined);\n\n if (isDsControl && 'error' in control && control.error !== this.error)\n control.error = this.error;\n\n const value = this.readValue(control);\n this.filled = value.length > 0;\n this.dirty = value !== this.initialValue;\n this.controlDisabled = Boolean(\n control.disabled || control.isInactive || control.hasAttribute('disabled')\n );\n this.controlRequired = Boolean(control.required || control.hasAttribute('required'));\n this.controlBorderless = control.hasBorder === false;\n this.controlSize = readControlSize(control);\n };\n\n private observeControlAttributes(control: FieldControl) {\n this.controlAttrObserver?.disconnect();\n this.controlAttrObserver = new MutationObserver(() => this.syncControl());\n this.controlAttrObserver.observe(control, {\n attributes: true,\n attributeFilter: ['has-border', 'size', 'class'],\n });\n }\n\n private handleLabelClick = (event: MouseEvent) => {\n if (!this.control?.tagName.startsWith('DS-')) return;\n event.preventDefault();\n this.control.setFocus?.();\n };\n\n private handleFocusIn = () => {\n this.focused = true;\n };\n\n private handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && this.el.contains(event.relatedTarget)) return;\n this.focused = false;\n this.touched = true;\n };\n\n private handleValueChange = () => {\n this.syncControl();\n };\n\n render() {\n return (\n <Host\n data-focused={this.focused ? '' : undefined}\n data-filled={this.filled ? '' : undefined}\n data-dirty={this.dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n data-invalid={this.error ? '' : undefined}\n data-disabled={this.controlDisabled ? '' : undefined}\n data-required={this.controlRequired ? '' : undefined}\n onFocusin={this.handleFocusIn}\n onFocusout={this.handleFocusOut}\n onInput={this.handleValueChange}\n onChange={this.handleValueChange}\n onDsChange={this.handleValueChange}\n >\n <div\n class={{\n field: true,\n 'ds-field-stack': true,\n 'ds-field-stack--supporting-inset': this.controlBorderless,\n [`ds-control--${this.controlSize}`]: true,\n }}\n >\n {this.hasLabel ? (\n <ds-text\n class=\"field__label\"\n as=\"label\"\n variant=\"text-body-small\"\n color=\"primary\"\n emphasis\n for={this.controlId}\n textId={this.labelId}\n onClick={this.handleLabelClick}\n >\n {this.label}\n </ds-text>\n ) : null}\n <div class=\"field__control\" ref={el => (this.controlContainer = el)}>\n <slot onSlotchange={this.syncControl} />\n </div>\n {this.renderedDescription && (\n <ds-text\n class=\"field__description\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"secondary\"\n textId={this.descriptionId}\n >\n {this.description}\n </ds-text>\n )}\n {this.renderedError && (\n <ds-text\n class=\"field__error\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </div>\n </Host>\n );\n }\n}\n",
|
|
225
230
|
"type": "registry:ui"
|
|
226
231
|
}
|
|
227
232
|
]
|