@ds-mo/ui 14.4.2 → 14.6.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 +34 -10
- package/dist/angular/ds-tooltip.d.ts +1 -1
- package/dist/angular/ds-tooltip.js +4 -3
- package/dist/components/ds-agent-questionnaire.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-nav.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-card-overview.js +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-filter-menu.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-composer.js +1 -1
- package/dist/components/ds-message-scroller.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-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-select.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/{p-CRLRjtsc.js → p-3r4TfcZX.js} +2 -2
- package/dist/components/{p-nImbYpwo.js → p-BFDKKBYh.js} +2 -2
- package/dist/components/{p-w64TmQd-.js → p-BGQUiJuJ.js} +2 -2
- package/dist/components/{p-DkPNCBA8.js → p-BX-R5EhL.js} +2 -2
- package/dist/components/p-BXPQmp7q.js +2 -0
- package/dist/components/p-BXPQmp7q.js.map +1 -0
- package/dist/components/{p-BGGm1NB1.js → p-BykcjUd6.js} +2 -2
- package/dist/components/{p-CvpsV-jC.js → p-C5230yJY.js} +2 -2
- package/dist/components/{p-o1v7Choa.js → p-C7-ylLcB.js} +2 -2
- package/dist/components/{p-Ba1l3Yot.js → p-CCpJcSTE.js} +2 -2
- package/dist/components/{p-B2hqUkg-.js → p-CDEME0Uc.js} +2 -2
- package/dist/components/{p-Ccq1RmOb.js → p-CRi7P5gQ.js} +2 -2
- package/dist/components/p-CRi7P5gQ.js.map +1 -0
- package/dist/components/{p-BXNLaPbw.js → p-DJ-n-gyr.js} +2 -2
- package/dist/components/{p-CXqVVdW2.js → p-DL12lNtv.js} +2 -2
- package/dist/components/{p-Dkkid-rh.js → p-DTf90AyI.js} +2 -2
- package/dist/components/{p-BiIdszv1.js → p-K_vg9Eng.js} +2 -2
- package/dist/components/{p-ohWEVGHg.js → p-os90t2-f.js} +2 -2
- package/dist/docs/components.json +162 -14
- package/dist/lib/utils/index.d.ts +1 -0
- package/dist/lib/utils/index.js +8 -0
- package/dist/lib/utils/index.js.map +3 -3
- package/dist/lib/utils/is-element-truncated.d.ts +6 -0
- package/dist/mcp-data/registry/menu.json +1 -1
- package/dist/mcp-data/registry/registry.json +33 -9
- package/dist/mcp-data/registry/table.json +34 -11
- package/dist/mcp-data/registry/tooltip.json +68 -7
- package/dist/styles/table.css +170 -42
- package/dist/types/components/Menu/Menu.d.ts +1 -0
- package/dist/types/components/Table/Table.d.ts +24 -0
- package/dist/types/components/Table/index.d.ts +1 -1
- package/dist/types/components/Table/table-action-menu.d.ts +15 -0
- package/dist/types/components/Table/table-cell-model.d.ts +41 -3
- package/dist/types/components/Table/table-model.d.ts +2 -1
- package/dist/types/components/Table/table-truncate.d.ts +6 -0
- package/dist/types/components/Table/table-types.d.ts +103 -8
- package/dist/types/components/Tooltip/Tooltip.d.ts +15 -0
- package/dist/types/components.d.ts +30 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/is-element-truncated.d.ts +7 -0
- package/package.json +1 -1
- package/dist/components/p-Ccq1RmOb.js.map +0 -1
- package/dist/components/p-CtOK5oZ1.js +0 -2
- package/dist/components/p-CtOK5oZ1.js.map +0 -1
- /package/dist/components/{p-CRLRjtsc.js.map → p-3r4TfcZX.js.map} +0 -0
- /package/dist/components/{p-nImbYpwo.js.map → p-BFDKKBYh.js.map} +0 -0
- /package/dist/components/{p-w64TmQd-.js.map → p-BGQUiJuJ.js.map} +0 -0
- /package/dist/components/{p-DkPNCBA8.js.map → p-BX-R5EhL.js.map} +0 -0
- /package/dist/components/{p-BGGm1NB1.js.map → p-BykcjUd6.js.map} +0 -0
- /package/dist/components/{p-CvpsV-jC.js.map → p-C5230yJY.js.map} +0 -0
- /package/dist/components/{p-o1v7Choa.js.map → p-C7-ylLcB.js.map} +0 -0
- /package/dist/components/{p-Ba1l3Yot.js.map → p-CCpJcSTE.js.map} +0 -0
- /package/dist/components/{p-B2hqUkg-.js.map → p-CDEME0Uc.js.map} +0 -0
- /package/dist/components/{p-BXNLaPbw.js.map → p-DJ-n-gyr.js.map} +0 -0
- /package/dist/components/{p-CXqVVdW2.js.map → p-DL12lNtv.js.map} +0 -0
- /package/dist/components/{p-Dkkid-rh.js.map → p-DTf90AyI.js.map} +0 -0
- /package/dist/components/{p-BiIdszv1.js.map → p-K_vg9Eng.js.map} +0 -0
- /package/dist/components/{p-ohWEVGHg.js.map → p-os90t2-f.js.map} +0 -0
|
@@ -45,12 +45,13 @@
|
|
|
45
45
|
"component:ds-button-filled",
|
|
46
46
|
"component:ds-button-unfilled",
|
|
47
47
|
"component:ds-panel-nav",
|
|
48
|
-
"component:ds-panel-tools"
|
|
48
|
+
"component:ds-panel-tools",
|
|
49
|
+
"component:ds-table"
|
|
49
50
|
],
|
|
50
51
|
"accessibility": [
|
|
51
52
|
"The slotted trigger must have its own accessible name, usually an aria-label that closely matches the visual tooltip label.",
|
|
52
53
|
"Tooltip content is supplementary and non-essential; touch input intentionally does not open it.",
|
|
53
|
-
"While open, the trigger is linked to the role=tooltip popup with aria-describedby; the relationship is removed when the popup leaves the DOM without disturbing consumer description IDs.",
|
|
54
|
+
"While open, the trigger is linked to the role=tooltip popup with aria-describedby by default; the relationship is removed when the popup leaves the DOM without disturbing consumer description IDs. Hosts that already expose the full string, such as a truncated table cell, disable that relationship.",
|
|
54
55
|
"Keyboard focus opens the tooltip and Escape dismisses it without moving focus."
|
|
55
56
|
],
|
|
56
57
|
"states": [
|
|
@@ -58,18 +59,20 @@
|
|
|
58
59
|
"An empty or whitespace-only label suppresses the popup while preserving the wrapper.",
|
|
59
60
|
"Only one tooltip remains active globally; warm handoff removes the previous popup before showing the next.",
|
|
60
61
|
"Disconnecting and reinserting the tooltip host rebinds the current slotted trigger.",
|
|
62
|
+
"A host may present the popup against an external anchor it already owns, without wrapping that trigger or binding hover on it.",
|
|
61
63
|
"The preferred side flips when it cannot fit, then alignment is clamped to viewport padding.",
|
|
62
64
|
"Reduced motion removes enter and exit fades while retaining the final visible or hidden state."
|
|
63
65
|
],
|
|
64
66
|
"responsiveBehavior": [
|
|
65
67
|
"The portaled popup follows trigger and popup resize, viewport resize, and nested scrolling while open.",
|
|
66
68
|
"Touch users rely on the trigger's accessible and visible context rather than tooltip disclosure.",
|
|
67
|
-
"Keep labels concise and non-wrapping
|
|
69
|
+
"Keep labels concise and non-wrapping by default; omitted table cell values may wrap at panel-xs."
|
|
68
70
|
],
|
|
69
71
|
"frameworkCaveats": {
|
|
70
72
|
"customElements": [
|
|
71
73
|
"Place exactly one trigger element as the component's light-DOM child.",
|
|
72
74
|
"Replacing the slotted trigger rebinds pointer and focus behavior automatically.",
|
|
75
|
+
"A table or similar host can present the popup against an overflowing text node instead of wrapping that node.",
|
|
73
76
|
"sideOffset, alignOffset, and delay accept numbers or TokoMo CSS values."
|
|
74
77
|
],
|
|
75
78
|
"react": [
|
|
@@ -122,6 +125,15 @@
|
|
|
122
125
|
"mutable": false,
|
|
123
126
|
"description": "Hover show delay before the tooltip appears.\nDefault: `--effect-animation-delay-medium-3` (1000ms). Accepts a number (ms)\nor a TokoMo time token / `var(--effect-animation-delay-*)`. Applies to the\ninitial hover/focus show only — after a recent dismiss, reopen is instant.\nPrefer the default; override only for denser chrome or rare/destructive actions."
|
|
124
127
|
},
|
|
128
|
+
"describedBy": {
|
|
129
|
+
"type": "boolean",
|
|
130
|
+
"resolvedType": "boolean",
|
|
131
|
+
"attribute": "described-by",
|
|
132
|
+
"default": "true",
|
|
133
|
+
"required": false,
|
|
134
|
+
"mutable": false,
|
|
135
|
+
"description": "Link the trigger with aria-describedby while the popup is open. Disable when\nthe label is already in the DOM, such as a truncated table cell."
|
|
136
|
+
},
|
|
125
137
|
"label": {
|
|
126
138
|
"type": "string",
|
|
127
139
|
"resolvedType": "string",
|
|
@@ -170,10 +182,30 @@
|
|
|
170
182
|
"required": false,
|
|
171
183
|
"mutable": false,
|
|
172
184
|
"description": "Control density (height, padding, type)."
|
|
185
|
+
},
|
|
186
|
+
"wrapLabel": {
|
|
187
|
+
"type": "boolean",
|
|
188
|
+
"resolvedType": "boolean",
|
|
189
|
+
"attribute": "wrap-label",
|
|
190
|
+
"default": "false",
|
|
191
|
+
"required": false,
|
|
192
|
+
"mutable": false,
|
|
193
|
+
"description": "Allow the label to wrap at a panel-xs max width instead of a single line."
|
|
173
194
|
}
|
|
174
195
|
},
|
|
175
196
|
"events": [],
|
|
176
|
-
"methods": [
|
|
197
|
+
"methods": [
|
|
198
|
+
{
|
|
199
|
+
"name": "dismiss",
|
|
200
|
+
"signature": "dismiss() => Promise<void>",
|
|
201
|
+
"description": "Dismiss a popup opened through presentFrom or the slotted trigger."
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "presentFrom",
|
|
205
|
+
"signature": "presentFrom(anchor: HTMLElement, label?: string) => Promise<void>",
|
|
206
|
+
"description": "Present against an external anchor the host already measured.\nDoes not bind hover or focus on that anchor — the owner drives show/hide."
|
|
207
|
+
}
|
|
208
|
+
],
|
|
177
209
|
"slots": []
|
|
178
210
|
},
|
|
179
211
|
"props": {
|
|
@@ -203,6 +235,15 @@
|
|
|
203
235
|
"mutable": false,
|
|
204
236
|
"description": "Hover show delay before the tooltip appears.\nDefault: `--effect-animation-delay-medium-3` (1000ms). Accepts a number (ms)\nor a TokoMo time token / `var(--effect-animation-delay-*)`. Applies to the\ninitial hover/focus show only — after a recent dismiss, reopen is instant.\nPrefer the default; override only for denser chrome or rare/destructive actions."
|
|
205
237
|
},
|
|
238
|
+
"describedBy": {
|
|
239
|
+
"type": "boolean",
|
|
240
|
+
"resolvedType": "boolean",
|
|
241
|
+
"attribute": "described-by",
|
|
242
|
+
"default": "true",
|
|
243
|
+
"required": false,
|
|
244
|
+
"mutable": false,
|
|
245
|
+
"description": "Link the trigger with aria-describedby while the popup is open. Disable when\nthe label is already in the DOM, such as a truncated table cell."
|
|
246
|
+
},
|
|
206
247
|
"label": {
|
|
207
248
|
"type": "string",
|
|
208
249
|
"resolvedType": "string",
|
|
@@ -251,10 +292,30 @@
|
|
|
251
292
|
"required": false,
|
|
252
293
|
"mutable": false,
|
|
253
294
|
"description": "Control density (height, padding, type)."
|
|
295
|
+
},
|
|
296
|
+
"wrapLabel": {
|
|
297
|
+
"type": "boolean",
|
|
298
|
+
"resolvedType": "boolean",
|
|
299
|
+
"attribute": "wrap-label",
|
|
300
|
+
"default": "false",
|
|
301
|
+
"required": false,
|
|
302
|
+
"mutable": false,
|
|
303
|
+
"description": "Allow the label to wrap at a panel-xs max width instead of a single line."
|
|
254
304
|
}
|
|
255
305
|
},
|
|
256
306
|
"events": [],
|
|
257
|
-
"methods": [
|
|
307
|
+
"methods": [
|
|
308
|
+
{
|
|
309
|
+
"name": "dismiss",
|
|
310
|
+
"signature": "dismiss() => Promise<void>",
|
|
311
|
+
"description": "Dismiss a popup opened through presentFrom or the slotted trigger."
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"name": "presentFrom",
|
|
315
|
+
"signature": "presentFrom(anchor: HTMLElement, label?: string) => Promise<void>",
|
|
316
|
+
"description": "Present against an external anchor the host already measured.\nDoes not bind hover or focus on that anchor — the owner drives show/hide."
|
|
317
|
+
}
|
|
318
|
+
],
|
|
258
319
|
"slots": [],
|
|
259
320
|
"exports": {
|
|
260
321
|
"customElement": "ds-tooltip",
|
|
@@ -295,12 +356,12 @@
|
|
|
295
356
|
"files": [
|
|
296
357
|
{
|
|
297
358
|
"path": "src/wc/components/Tooltip/Tooltip.css",
|
|
298
|
-
"content": "@import '../../utils/control-density.css';\n@import '../../utils/key-hint.css';\n@import '../../utils/forced-colors.css';\n\n.tooltip-popup {\n box-sizing: border-box;\n background-color: var(--color-background-primary);\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n box-shadow: var(--effect-elevation-elevated-floating);\n white-space: nowrap;\n pointer-events: none;\n animation: tooltipFadeIn var(--effect-motion-short-2) forwards;\n will-change: transform;\n backface-visibility: hidden;\n}\n\n/* Warm handoff between nearby triggers — no enter fade (avoids 20–100ms lag). */\n.tooltip-popup--instant {\n animation: none;\n opacity: 1;\n}\n\n.tooltip-popup--closing {\n animation: tooltipFadeOut var(--effect-motion-short-3) forwards;\n}\n\n/* Density: md / sm / xs via `.ds-control--*` on `.tooltip-inner`. */\n.tooltip-inner {\n box-sizing: border-box;\n height: var(--ds-control-height, var(--dimension-size-400));\n display: flex;\n align-items: center;\n gap: var(--ds-control-gap, var(--dimension-space-050));\n padding: 0 var(--ds-control-padding-inline, var(--dimension-space-075));\n}\n\n/*\n * Label layout lives on ds-text (metric-box + control label-inset).\n * NOTE: Stencil scopes this to `ds-text.sc-ds-tooltip`, but portaled\n * createElement ds-text loses that class when Text's <Host class> renders.\n * `renderPopupContent` therefore also sets these as inline styles.\n */\n.tooltip-inner > ds-text {\n flex-shrink: 0;\n min-width: max-content;\n max-width: none;\n padding: 0 var(--ds-control-label-inset, var(--dimension-space-025));\n color: var(--color-foreground-primary);\n}\n\n@keyframes tooltipFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes tooltipFadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tooltip-popup,\n .tooltip-popup--instant {\n animation: none;\n opacity: 1;\n }\n\n .tooltip-popup--closing {\n animation: none;\n opacity: 0;\n }\n}\n\n@media (forced-colors: active) {\n .tooltip-popup {\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",
|
|
359
|
+
"content": "@import '../../utils/control-density.css';\n@import '../../utils/key-hint.css';\n@import '../../utils/forced-colors.css';\n\n.tooltip-popup {\n box-sizing: border-box;\n background-color: var(--color-background-primary);\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n box-shadow: var(--effect-elevation-elevated-floating);\n white-space: nowrap;\n pointer-events: none;\n animation: tooltipFadeIn var(--effect-motion-short-2) forwards;\n will-change: transform;\n backface-visibility: hidden;\n}\n\n/* Warm handoff between nearby triggers — no enter fade (avoids 20–100ms lag). */\n.tooltip-popup--instant {\n animation: none;\n opacity: 1;\n}\n\n.tooltip-popup--closing {\n animation: tooltipFadeOut var(--effect-motion-short-3) forwards;\n}\n\n/* Density: md / sm / xs via `.ds-control--*` on `.tooltip-inner`. */\n.tooltip-inner {\n box-sizing: border-box;\n height: var(--ds-control-height, var(--dimension-size-400));\n display: flex;\n align-items: center;\n gap: var(--ds-control-gap, var(--dimension-space-050));\n padding: 0 var(--ds-control-padding-inline, var(--dimension-space-075));\n}\n\n/*\n * Label layout lives on ds-text (metric-box + control label-inset).\n * NOTE: Stencil scopes this to `ds-text.sc-ds-tooltip`, but portaled\n * createElement ds-text loses that class when Text's <Host class> renders.\n * `renderPopupContent` therefore also sets these as inline styles.\n */\n.tooltip-inner > ds-text {\n flex-shrink: 0;\n min-width: max-content;\n max-width: none;\n padding: 0 var(--ds-control-label-inset, var(--dimension-space-025));\n color: var(--color-foreground-primary);\n}\n\n.tooltip-popup--wrap {\n white-space: normal;\n max-inline-size: var(--dimension-panel-width-xs);\n}\n\n.tooltip-popup--wrap .tooltip-inner {\n height: auto;\n min-height: var(--ds-control-height, var(--dimension-size-400));\n padding-block: var(--dimension-space-050);\n align-items: flex-start;\n}\n\n.tooltip-popup--wrap .tooltip-inner > ds-text {\n flex: 1 1 auto;\n min-width: 0;\n max-width: 100%;\n white-space: normal;\n}\n\n@keyframes tooltipFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes tooltipFadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tooltip-popup,\n .tooltip-popup--instant {\n animation: none;\n opacity: 1;\n }\n\n .tooltip-popup--closing {\n animation: none;\n opacity: 0;\n }\n}\n\n@media (forced-colors: active) {\n .tooltip-popup {\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",
|
|
299
360
|
"type": "registry:ui"
|
|
300
361
|
},
|
|
301
362
|
{
|
|
302
363
|
"path": "src/wc/components/Tooltip/Tooltip.tsx",
|
|
303
|
-
"content": "import { Component, Prop, Element, Watch, h, Host } from '@stencil/core';\nimport {\n CONTROL_TEXT_VARIANT,\n resolveCssLengthPx,\n resolveCssTimeMs,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n} from '../../utils';\nimport type { TextVariant } from '../Text/text-types';\nimport { shortcutKeyLabels } from '../../utils/shortcut-key';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\n// Side-effect: register `ds-text` — the portal builds it via createElement, not JSX.\nimport '../Text/Text';\n\nexport type TooltipSide = 'top' | 'right' | 'bottom' | 'left';\nexport type TooltipAlign = 'start' | 'center' | 'end';\nexport type TooltipSize = 'md' | 'sm' | 'xs';\n\nconst FALLBACK_HEIGHT: Record<TooltipSize, string> = {\n md: TOKEN_DEFAULTS.size400,\n sm: TOKEN_DEFAULTS.size300,\n xs: TOKEN_DEFAULTS.size200,\n};\n\nlet lastDismissedAt = 0;\nlet tooltipIdCounter = 0;\n/** Currently open tooltip — force-cleared on warm handoff so tips don't cross-fade. */\nlet activeTooltip: Tooltip | null = null;\n\ntype DsTextEl = HTMLElement & {\n as: string;\n variant: TextVariant;\n emphasis: boolean;\n color: string;\n wrap: string;\n};\n\nfunction createDsText(opts: {\n variant: TextVariant;\n emphasis?: boolean;\n color?: string;\n wrap?: 'wrap' | 'nowrap' | 'balance' | 'pretty';\n text: string;\n}): DsTextEl {\n const el = document.createElement('ds-text') as DsTextEl;\n el.as = 'span';\n el.variant = opts.variant;\n el.emphasis = opts.emphasis ?? false;\n el.color = opts.color ?? 'inherit';\n if (opts.wrap) el.wrap = opts.wrap;\n el.textContent = opts.text;\n return el;\n}\n\n/**\n * Control-density label styles for portaled ds-text.\n * Scoped CSS requires `sc-ds-tooltip` on the host, but Text's `<Host class>`\n * overwrites imperative className — so these must be inline to stick.\n */\nfunction styleTooltipLabel(el: HTMLElement) {\n el.style.flexShrink = '0';\n el.style.minWidth = 'max-content';\n el.style.maxWidth = 'none';\n el.style.padding = '0 var(--ds-control-label-inset, var(--dimension-space-025))';\n el.style.color = 'var(--color-foreground-primary)';\n}\n\n/**\n * Imperative body portal for the popup.\n * Stencil must not own the portaled node — moving a VDOM child to `document.body`\n * causes recreate → re-portal → @State loops that freeze the page.\n */\n@Component({\n tag: 'ds-tooltip',\n styleUrl: 'Tooltip.css',\n scoped: true,\n})\nexport class Tooltip {\n @Element() el!: HTMLElement;\n\n /**\n * Tooltip text. Empty/whitespace skips show — useful when the wrapper must stay\n * mounted for layout/animation stability (e.g. PanelNav expand/collapse).\n */\n @Prop() label!: string;\n\n /** Control density (height, padding, type). */\n @Prop() size: TooltipSize = 'md';\n\n @Prop() side: TooltipSide = 'top';\n @Prop() align: TooltipAlign = 'center';\n /** Gap between anchor and tooltip — number (px) or TokoMo length. */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n /**\n * Hover show delay before the tooltip appears.\n * Default: `--effect-animation-delay-medium-3` (1000ms). Accepts a number (ms)\n * or a TokoMo time token / `var(--effect-animation-delay-*)`. Applies to the\n * initial hover/focus show only — after a recent dismiss, reopen is instant.\n * Prefer the default; override only for denser chrome or rare/destructive actions.\n */\n @Prop() delay: number | string = TOKEN_DEFAULTS.animationDelayMedium3;\n @Prop() shortcutKey: string | undefined;\n @Prop() shortcutKeyPosition: 'start' | 'end' = 'end';\n\n private tooltipId = `ds-tooltip-${++tooltipIdCounter}`;\n private delayTimer: ReturnType<typeof setTimeout> | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private anchor: HTMLElement | null = null;\n private popupEl: HTMLElement | null = null;\n private anchorObserver: MutationObserver | null = null;\n private didLoad = false;\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.anchor,\n getPopup: () => this.popupEl,\n measure: (anchor, tip) => ({\n anchorRect: anchor.getBoundingClientRect(),\n // Fall back to token metrics until `ds-text` upgrades and reports a width.\n popupWidth: tip.offsetWidth || this.tooltipFallbackWidthPx,\n popupHeight: tip.offsetHeight || this.tooltipFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: this.alignOffsetPx,\n viewportPadPx: this.viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }),\n apply: ({ x, y, resolvedSide }) => {\n if (!this.popupEl) return;\n this.popupEl.dataset.side = resolvedSide;\n this.popupEl.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;\n },\n liveUpdate: 'frame',\n observeResize: true,\n });\n private skipEnterAnimation = false;\n private isOpen = false;\n private lastInteractionWasKeyboard = false;\n private pointerEnterHandler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n this.show();\n };\n private pointerLeaveHandler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n this.hide();\n };\n private pointerDownHandler = (event: PointerEvent) => {\n this.lastInteractionWasKeyboard = false;\n if (event.pointerType === 'touch' && (this.isOpen || this.popupEl)) this.hideInstant();\n };\n private keyDownHandler = () => {\n this.lastInteractionWasKeyboard = true;\n };\n /** focusin/focusout so nested focus targets (e.g. ds-button-* inner button) still open the tip. */\n private focusHandler = () => {\n if (this.lastInteractionWasKeyboard || this.anchor?.matches(':focus-visible')) this.show();\n };\n private blurHandler: (e: FocusEvent) => void = (e) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.anchor?.contains(next)) return;\n this.hide();\n };\n private escapeHandler = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || !this.isOpen) return;\n event.preventDefault();\n this.hide();\n };\n\n connectedCallback() {\n if (!this.didLoad) return;\n this.connectAnchor();\n }\n\n componentDidLoad() {\n this.didLoad = true;\n this.connectAnchor();\n }\n\n private connectAnchor() {\n this.bindAnchor();\n if (this.anchorObserver) return;\n this.anchorObserver = new MutationObserver(() => this.handleSlotChange());\n this.anchorObserver.observe(this.el, { childList: true });\n }\n\n disconnectedCallback() {\n if (activeTooltip === this) activeTooltip = null;\n this.anchorObserver?.disconnect();\n this.anchorObserver = null;\n this.unbindAnchor();\n this.clearTimers();\n this.destroyPopup();\n }\n\n @Watch('label')\n @Watch('size')\n @Watch('side')\n @Watch('align')\n @Watch('sideOffset')\n @Watch('alignOffset')\n @Watch('shortcutKey')\n @Watch('shortcutKeyPosition')\n onContentOrPositionChange() {\n if (!this.label?.trim()) {\n if (this.isOpen || this.popupEl) this.hideInstant();\n return;\n }\n if (!this.popupEl || !this.isOpen) return;\n this.renderPopupContent();\n this.schedulePosition();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get hoverDelayMs(): number {\n return resolveCssTimeMs(this.delay, TOKEN_DEFAULTS.animationDelayMedium3);\n }\n\n private get instantReopenMs(): number {\n return resolveCssTimeMs(\n TOKEN_DEFAULTS.animationDurationMedium1,\n TOKEN_DEFAULTS.animationDurationMedium1,\n );\n }\n\n private get fadeOutMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort3, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private get tooltipFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.tooltipFallbackWidth, TOKEN_DEFAULTS.tooltipFallbackWidth);\n }\n\n private get tooltipFallbackHeightPx(): number {\n const token = FALLBACK_HEIGHT[this.size];\n return resolveCssLengthPx(token, token);\n }\n\n private bindAnchor() {\n const slot = this.el.querySelector('slot') as HTMLSlotElement | null;\n const slotted = slot?.assignedElements?.() ?? [];\n const child = (slotted[0] ?? this.el.firstElementChild) as HTMLElement | null;\n if (!child || child === this.anchor) return;\n this.anchor = child;\n child.addEventListener('pointerenter', this.pointerEnterHandler);\n child.addEventListener('pointerleave', this.pointerLeaveHandler);\n child.addEventListener('pointerdown', this.pointerDownHandler);\n child.addEventListener('keydown', this.keyDownHandler);\n child.addEventListener('focusin', this.focusHandler);\n child.addEventListener('focusout', this.blurHandler);\n }\n\n private unbindAnchor() {\n if (!this.anchor) return;\n this.unlinkDescribedBy();\n this.anchor.removeEventListener('pointerenter', this.pointerEnterHandler);\n this.anchor.removeEventListener('pointerleave', this.pointerLeaveHandler);\n this.anchor.removeEventListener('pointerdown', this.pointerDownHandler);\n this.anchor.removeEventListener('keydown', this.keyDownHandler);\n this.anchor.removeEventListener('focusin', this.focusHandler);\n this.anchor.removeEventListener('focusout', this.blurHandler);\n this.anchor = null;\n }\n\n private linkDescribedBy() {\n if (!this.anchor || !this.popupEl) return;\n const ids = new Set((this.anchor.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean));\n ids.add(this.tooltipId);\n this.anchor.setAttribute('aria-describedby', Array.from(ids).join(' '));\n }\n\n private unlinkDescribedBy() {\n if (!this.anchor) return;\n const ids = (this.anchor.getAttribute('aria-describedby') ?? '')\n .split(/\\s+/)\n .filter(id => id && id !== this.tooltipId);\n if (ids.length) this.anchor.setAttribute('aria-describedby', ids.join(' '));\n else this.anchor.removeAttribute('aria-describedby');\n }\n\n private handleSlotChange = () => {\n const slot = this.el.querySelector('slot') as HTMLSlotElement | null;\n const next = (slot?.assignedElements?.()[0] ?? this.el.firstElementChild) as HTMLElement | null;\n if (next === this.anchor) return;\n if (this.isOpen || this.popupEl) this.hideInstant();\n this.unbindAnchor();\n this.bindAnchor();\n };\n\n private clearTimers() {\n if (this.delayTimer) { clearTimeout(this.delayTimer); this.delayTimer = null; }\n if (this.closeTimer) { clearTimeout(this.closeTimer); this.closeTimer = null; }\n }\n\n private setupOpenListeners() {\n // Binds scroll/resize + the resize observer; placement itself is scheduled\n // by schedulePosition() so the label restyle stays interleaved.\n this.position.observe();\n document.addEventListener('keydown', this.escapeHandler);\n }\n\n private teardownOpenListeners() {\n this.position.unobserve();\n document.removeEventListener('keydown', this.escapeHandler);\n }\n\n private destroyPopup() {\n this.teardownOpenListeners();\n this.unlinkDescribedBy();\n if (!this.popupEl) return;\n this.popupEl.remove();\n this.popupEl = null;\n }\n\n /**\n * Position after `ds-text` upgrades and lays out. Measuring synchronously in\n * `mountPopup` reads a collapsed/fallback width and places left/right tips wrong.\n */\n private schedulePosition() {\n if (!this.popupEl) return;\n\n const reapplyLabelStyles = () => {\n const label = this.popupEl?.querySelector(':scope > .tooltip-inner > ds-text');\n if (label instanceof HTMLElement) styleTooltipLabel(label);\n };\n\n requestAnimationFrame(() => {\n reapplyLabelStyles();\n this.position.update();\n requestAnimationFrame(() => {\n reapplyLabelStyles();\n this.position.update();\n });\n });\n\n // The popup remounts on every show, so re-target the observer bound in start().\n this.position.observeResizeTargets();\n }\n\n /** Drop the open tip immediately (warm handoff to another tooltip). */\n private hideInstant() {\n this.clearTimers();\n lastDismissedAt = Date.now();\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n }\n\n private show() {\n // Empty label = wrapper present for DOM stability (e.g. PanelNav expand/collapse)\n // but no tip should appear.\n if (!this.label?.trim()) return;\n this.clearTimers();\n let instant = Date.now() - lastDismissedAt < this.instantReopenMs;\n if (activeTooltip && activeTooltip !== this) {\n activeTooltip.hideInstant();\n instant = true;\n }\n // eslint-disable-next-line @typescript-eslint/no-this-alias -- module singleton tracks the active instance\n activeTooltip = this;\n this.skipEnterAnimation = instant;\n if (instant) {\n this.mountPopup();\n return;\n }\n this.delayTimer = setTimeout(() => {\n this.delayTimer = null;\n if (!this.label?.trim()) return;\n this.mountPopup();\n }, this.hoverDelayMs);\n }\n\n private hide() {\n this.clearTimers();\n if (!this.popupEl && !this.isOpen) return;\n lastDismissedAt = Date.now();\n if (!this.popupEl) {\n this.isOpen = false;\n return;\n }\n this.popupEl.classList.remove('tooltip-popup--instant');\n this.popupEl.classList.add('tooltip-popup--closing');\n if (this.fadeOutMs === 0) {\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n }, this.fadeOutMs);\n }\n\n private mountPopup() {\n if (!this.popupEl) {\n const tip = document.createElement('div');\n tip.id = this.tooltipId;\n tip.setAttribute('role', 'tooltip');\n // `sc-ds-tooltip` is required for Stencil scoped CSS to match outside the host.\n tip.className = 'tooltip-popup ds-forced-colors-scope sc-ds-tooltip';\n Object.assign(tip.style, {\n position: 'fixed',\n left: '0',\n top: '0',\n zIndex: 'var(--dimension-z-index-tooltip)',\n pointerEvents: 'none',\n });\n document.body.appendChild(tip);\n this.popupEl = tip;\n }\n\n this.setupOpenListeners();\n this.linkDescribedBy();\n this.popupEl.classList.toggle('tooltip-popup--instant', this.skipEnterAnimation);\n this.popupEl.classList.remove('tooltip-popup--closing');\n this.renderPopupContent();\n this.schedulePosition();\n this.isOpen = true;\n }\n\n private renderPopupContent() {\n if (!this.popupEl) return;\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const density =\n this.size === 'md' ? 'ds-control--md' : this.size === 'sm' ? 'ds-control--sm' : 'ds-control--xs';\n // Every layout node needs `sc-ds-tooltip` so scoped selectors (`.foo.sc-ds-tooltip`) match.\n const sc = 'sc-ds-tooltip';\n\n const inner = document.createElement('div');\n inner.className = `tooltip-inner ${density} ${sc}`;\n\n const appendShortcut = (position: 'start' | 'end') => {\n if (!this.shortcutKey || this.shortcutKeyPosition !== position) return;\n const group = document.createElement('span');\n group.className = `key-hint-group ${sc}`;\n group.setAttribute('aria-hidden', 'true');\n\n for (const label of shortcutKeyLabels(this.shortcutKey)) {\n const hint = document.createElement('span');\n const wide = Array.from(label).length > 1 ? ' key-hint--wide' : '';\n hint.className = `key-hint${wide} ${sc}`;\n const text = document.createElement('span');\n text.className = `key-hint__label ${sc}`;\n text.textContent = label;\n hint.appendChild(text);\n group.appendChild(hint);\n }\n inner.appendChild(group);\n };\n\n appendShortcut('start');\n\n // ds-text is the layout box (metric-box + 2px control label-inset).\n // Inline styles required — see styleTooltipLabel.\n const label = createDsText({ variant: textVariant, wrap: 'nowrap', text: this.label });\n styleTooltipLabel(label);\n inner.appendChild(label);\n\n appendShortcut('end');\n\n this.popupEl.replaceChildren(inner);\n }\n\n render() {\n return (\n <Host style={{ display: 'contents' }}>\n <slot onSlotchange={this.handleSlotChange} />\n </Host>\n );\n }\n}\n",
|
|
364
|
+
"content": "import { Component, Prop, Element, Method, Watch, h, Host } from '@stencil/core';\nimport {\n CONTROL_TEXT_VARIANT,\n resolveCssLengthPx,\n resolveCssTimeMs,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n} from '../../utils';\nimport type { TextVariant } from '../Text/text-types';\nimport { shortcutKeyLabels } from '../../utils/shortcut-key';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\n// Side-effect: register `ds-text` — the portal builds it via createElement, not JSX.\nimport '../Text/Text';\n\nexport type TooltipSide = 'top' | 'right' | 'bottom' | 'left';\nexport type TooltipAlign = 'start' | 'center' | 'end';\nexport type TooltipSize = 'md' | 'sm' | 'xs';\n\nconst FALLBACK_HEIGHT: Record<TooltipSize, string> = {\n md: TOKEN_DEFAULTS.size400,\n sm: TOKEN_DEFAULTS.size300,\n xs: TOKEN_DEFAULTS.size200,\n};\n\nlet lastDismissedAt = 0;\nlet tooltipIdCounter = 0;\n/** Currently open tooltip — force-cleared on warm handoff so tips don't cross-fade. */\nlet activeTooltip: Tooltip | null = null;\n\ntype DsTextEl = HTMLElement & {\n as: string;\n variant: TextVariant;\n emphasis: boolean;\n color: string;\n wrap: string;\n};\n\nfunction createDsText(opts: {\n variant: TextVariant;\n emphasis?: boolean;\n color?: string;\n wrap?: 'wrap' | 'nowrap' | 'balance' | 'pretty';\n text: string;\n}): DsTextEl {\n const el = document.createElement('ds-text') as DsTextEl;\n el.as = 'span';\n el.variant = opts.variant;\n el.emphasis = opts.emphasis ?? false;\n el.color = opts.color ?? 'inherit';\n if (opts.wrap) el.wrap = opts.wrap;\n el.textContent = opts.text;\n return el;\n}\n\n/**\n * Control-density label styles for portaled ds-text.\n * Scoped CSS requires `sc-ds-tooltip` on the host, but Text's `<Host class>`\n * overwrites imperative className — so these must be inline to stick.\n */\nfunction styleTooltipLabel(el: HTMLElement, wrapLabel = false) {\n el.style.flexShrink = wrapLabel ? '1' : '0';\n el.style.minWidth = wrapLabel ? '0' : 'max-content';\n el.style.maxWidth = wrapLabel ? '100%' : 'none';\n el.style.whiteSpace = wrapLabel ? 'normal' : '';\n el.style.padding = '0 var(--ds-control-label-inset, var(--dimension-space-025))';\n el.style.color = 'var(--color-foreground-primary)';\n}\n\n/**\n * Imperative body portal for the popup.\n * Stencil must not own the portaled node — moving a VDOM child to `document.body`\n * causes recreate → re-portal → @State loops that freeze the page.\n */\n@Component({\n tag: 'ds-tooltip',\n styleUrl: 'Tooltip.css',\n scoped: true,\n})\nexport class Tooltip {\n @Element() el!: HTMLElement;\n\n /**\n * Tooltip text. Empty/whitespace skips show — useful when the wrapper must stay\n * mounted for layout/animation stability (e.g. PanelNav expand/collapse).\n */\n @Prop() label!: string;\n\n /** Control density (height, padding, type). */\n @Prop() size: TooltipSize = 'md';\n\n @Prop() side: TooltipSide = 'top';\n @Prop() align: TooltipAlign = 'center';\n /** Gap between anchor and tooltip — number (px) or TokoMo length. */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n /**\n * Hover show delay before the tooltip appears.\n * Default: `--effect-animation-delay-medium-3` (1000ms). Accepts a number (ms)\n * or a TokoMo time token / `var(--effect-animation-delay-*)`. Applies to the\n * initial hover/focus show only — after a recent dismiss, reopen is instant.\n * Prefer the default; override only for denser chrome or rare/destructive actions.\n */\n @Prop() delay: number | string = TOKEN_DEFAULTS.animationDelayMedium3;\n @Prop() shortcutKey: string | undefined;\n @Prop() shortcutKeyPosition: 'start' | 'end' = 'end';\n /**\n * Link the trigger with aria-describedby while the popup is open. Disable when\n * the label is already in the DOM, such as a truncated table cell.\n */\n @Prop() describedBy = true;\n /** Allow the label to wrap at a panel-xs max width instead of a single line. */\n @Prop() wrapLabel = false;\n\n private tooltipId = `ds-tooltip-${++tooltipIdCounter}`;\n private delayTimer: ReturnType<typeof setTimeout> | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private anchor: HTMLElement | null = null;\n private popupEl: HTMLElement | null = null;\n private anchorObserver: MutationObserver | null = null;\n private didLoad = false;\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.anchor,\n getPopup: () => this.popupEl,\n measure: (anchor, tip) => ({\n anchorRect: anchor.getBoundingClientRect(),\n // Fall back to token metrics until `ds-text` upgrades and reports a width.\n popupWidth: tip.offsetWidth || this.tooltipFallbackWidthPx,\n popupHeight: tip.offsetHeight || this.tooltipFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: this.alignOffsetPx,\n viewportPadPx: this.viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }),\n apply: ({ x, y, resolvedSide }) => {\n if (!this.popupEl) return;\n this.popupEl.dataset.side = resolvedSide;\n this.popupEl.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;\n },\n liveUpdate: 'frame',\n observeResize: true,\n });\n private skipEnterAnimation = false;\n private isOpen = false;\n private programmaticAnchor = false;\n private lastInteractionWasKeyboard = false;\n private pointerEnterHandler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n this.show();\n };\n private pointerLeaveHandler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n this.hide();\n };\n private pointerDownHandler = (event: PointerEvent) => {\n this.lastInteractionWasKeyboard = false;\n if (event.pointerType === 'touch' && (this.isOpen || this.popupEl)) this.hideInstant();\n };\n private keyDownHandler = () => {\n this.lastInteractionWasKeyboard = true;\n };\n /** focusin/focusout so nested focus targets (e.g. ds-button-* inner button) still open the tip. */\n private focusHandler = () => {\n if (this.lastInteractionWasKeyboard || this.anchor?.matches(':focus-visible')) this.show();\n };\n private blurHandler: (e: FocusEvent) => void = (e) => {\n const next = e.relatedTarget as Node | null;\n if (next && this.anchor?.contains(next)) return;\n this.hide();\n };\n private escapeHandler = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || !this.isOpen) return;\n event.preventDefault();\n this.hide();\n };\n\n connectedCallback() {\n if (!this.didLoad) return;\n this.connectAnchor();\n }\n\n componentDidLoad() {\n this.didLoad = true;\n this.connectAnchor();\n }\n\n private connectAnchor() {\n this.bindAnchor();\n if (this.anchorObserver) return;\n this.anchorObserver = new MutationObserver(() => this.handleSlotChange());\n this.anchorObserver.observe(this.el, { childList: true });\n }\n\n disconnectedCallback() {\n if (activeTooltip === this) activeTooltip = null;\n this.anchorObserver?.disconnect();\n this.anchorObserver = null;\n this.unbindAnchor();\n this.clearTimers();\n this.destroyPopup();\n }\n\n @Watch('label')\n @Watch('size')\n @Watch('side')\n @Watch('align')\n @Watch('sideOffset')\n @Watch('alignOffset')\n @Watch('shortcutKey')\n @Watch('shortcutKeyPosition')\n @Watch('wrapLabel')\n onContentOrPositionChange() {\n if (!this.label?.trim()) {\n if (this.isOpen || this.popupEl) this.hideInstant();\n return;\n }\n if (!this.popupEl || !this.isOpen) return;\n this.renderPopupContent();\n this.schedulePosition();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get hoverDelayMs(): number {\n return resolveCssTimeMs(this.delay, TOKEN_DEFAULTS.animationDelayMedium3);\n }\n\n private get instantReopenMs(): number {\n return resolveCssTimeMs(\n TOKEN_DEFAULTS.animationDurationMedium1,\n TOKEN_DEFAULTS.animationDurationMedium1,\n );\n }\n\n private get fadeOutMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort3, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private get tooltipFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.tooltipFallbackWidth, TOKEN_DEFAULTS.tooltipFallbackWidth);\n }\n\n private get tooltipFallbackHeightPx(): number {\n const token = FALLBACK_HEIGHT[this.size];\n return resolveCssLengthPx(token, token);\n }\n\n /**\n * Present against an external anchor the host already measured.\n * Does not bind hover or focus on that anchor — the owner drives show/hide.\n */\n @Method()\n async presentFrom(anchor: HTMLElement, label?: string) {\n if (label != null) this.label = label;\n if (!this.label?.trim()) {\n this.hide();\n return;\n }\n const sameAnchor = this.anchor === anchor;\n if (!sameAnchor) {\n this.unbindAnchor();\n this.anchor = anchor;\n this.programmaticAnchor = true;\n if (this.popupEl && this.describedBy) this.linkDescribedBy();\n }\n if (this.popupEl) {\n this.renderPopupContent();\n this.schedulePosition();\n return;\n }\n if (this.delayTimer && sameAnchor) return;\n this.show();\n }\n\n /** Dismiss a popup opened through presentFrom or the slotted trigger. */\n @Method()\n async dismiss() {\n this.hide();\n }\n\n private bindAnchor() {\n if (this.programmaticAnchor) return;\n const slot = this.el.querySelector('slot') as HTMLSlotElement | null;\n const slotted = slot?.assignedElements?.() ?? [];\n const child = (slotted[0] ?? this.el.firstElementChild) as HTMLElement | null;\n if (!child || child.tagName === 'SLOT' || child === this.anchor) return;\n this.anchor = child;\n child.addEventListener('pointerenter', this.pointerEnterHandler);\n child.addEventListener('pointerleave', this.pointerLeaveHandler);\n child.addEventListener('pointerdown', this.pointerDownHandler);\n child.addEventListener('keydown', this.keyDownHandler);\n child.addEventListener('focusin', this.focusHandler);\n child.addEventListener('focusout', this.blurHandler);\n }\n\n private unbindAnchor() {\n if (!this.anchor) return;\n this.unlinkDescribedBy();\n if (!this.programmaticAnchor) {\n this.anchor.removeEventListener('pointerenter', this.pointerEnterHandler);\n this.anchor.removeEventListener('pointerleave', this.pointerLeaveHandler);\n this.anchor.removeEventListener('pointerdown', this.pointerDownHandler);\n this.anchor.removeEventListener('keydown', this.keyDownHandler);\n this.anchor.removeEventListener('focusin', this.focusHandler);\n this.anchor.removeEventListener('focusout', this.blurHandler);\n }\n this.anchor = null;\n this.programmaticAnchor = false;\n }\n\n private linkDescribedBy() {\n if (!this.describedBy || !this.anchor || !this.popupEl) return;\n const ids = new Set((this.anchor.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean));\n ids.add(this.tooltipId);\n this.anchor.setAttribute('aria-describedby', Array.from(ids).join(' '));\n }\n\n private unlinkDescribedBy() {\n if (!this.anchor) return;\n const ids = (this.anchor.getAttribute('aria-describedby') ?? '')\n .split(/\\s+/)\n .filter(id => id && id !== this.tooltipId);\n if (ids.length) this.anchor.setAttribute('aria-describedby', ids.join(' '));\n else this.anchor.removeAttribute('aria-describedby');\n }\n\n private handleSlotChange = () => {\n if (this.programmaticAnchor) return;\n const slot = this.el.querySelector('slot') as HTMLSlotElement | null;\n const next = (slot?.assignedElements?.()[0] ?? this.el.firstElementChild) as HTMLElement | null;\n if (next === this.anchor || next?.tagName === 'SLOT') return;\n if (this.isOpen || this.popupEl) this.hideInstant();\n this.unbindAnchor();\n this.bindAnchor();\n };\n\n private clearTimers() {\n if (this.delayTimer) { clearTimeout(this.delayTimer); this.delayTimer = null; }\n if (this.closeTimer) { clearTimeout(this.closeTimer); this.closeTimer = null; }\n }\n\n private setupOpenListeners() {\n // Binds scroll/resize + the resize observer; placement itself is scheduled\n // by schedulePosition() so the label restyle stays interleaved.\n this.position.observe();\n document.addEventListener('keydown', this.escapeHandler);\n }\n\n private teardownOpenListeners() {\n this.position.unobserve();\n document.removeEventListener('keydown', this.escapeHandler);\n }\n\n private destroyPopup() {\n this.teardownOpenListeners();\n this.unlinkDescribedBy();\n if (!this.popupEl) return;\n this.popupEl.remove();\n this.popupEl = null;\n }\n\n /**\n * Position after `ds-text` upgrades and lays out. Measuring synchronously in\n * `mountPopup` reads a collapsed/fallback width and places left/right tips wrong.\n */\n private schedulePosition() {\n if (!this.popupEl) return;\n\n const reapplyLabelStyles = () => {\n const label = this.popupEl?.querySelector(':scope > .tooltip-inner > ds-text');\n if (label instanceof HTMLElement) styleTooltipLabel(label, this.wrapLabel);\n };\n\n requestAnimationFrame(() => {\n reapplyLabelStyles();\n this.position.update();\n requestAnimationFrame(() => {\n reapplyLabelStyles();\n this.position.update();\n });\n });\n\n // The popup remounts on every show, so re-target the observer bound in start().\n this.position.observeResizeTargets();\n }\n\n /** Drop the open tip immediately (warm handoff to another tooltip). */\n private hideInstant() {\n this.clearTimers();\n lastDismissedAt = Date.now();\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n }\n\n private show() {\n // Empty label = wrapper present for DOM stability (e.g. PanelNav expand/collapse)\n // but no tip should appear.\n if (!this.label?.trim()) return;\n this.clearTimers();\n let instant = Date.now() - lastDismissedAt < this.instantReopenMs;\n if (activeTooltip && activeTooltip !== this) {\n activeTooltip.hideInstant();\n instant = true;\n }\n // eslint-disable-next-line @typescript-eslint/no-this-alias -- module singleton tracks the active instance\n activeTooltip = this;\n this.skipEnterAnimation = instant;\n if (instant) {\n this.mountPopup();\n return;\n }\n this.delayTimer = setTimeout(() => {\n this.delayTimer = null;\n if (!this.label?.trim()) return;\n this.mountPopup();\n }, this.hoverDelayMs);\n }\n\n private hide() {\n this.clearTimers();\n if (!this.popupEl && !this.isOpen) return;\n lastDismissedAt = Date.now();\n if (!this.popupEl) {\n this.isOpen = false;\n return;\n }\n this.popupEl.classList.remove('tooltip-popup--instant');\n this.popupEl.classList.add('tooltip-popup--closing');\n if (this.fadeOutMs === 0) {\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n if (activeTooltip === this) activeTooltip = null;\n this.destroyPopup();\n this.isOpen = false;\n this.skipEnterAnimation = false;\n }, this.fadeOutMs);\n }\n\n private mountPopup() {\n if (!this.popupEl) {\n const tip = document.createElement('div');\n tip.id = this.tooltipId;\n tip.setAttribute('role', 'tooltip');\n // `sc-ds-tooltip` is required for Stencil scoped CSS to match outside the host.\n tip.className = 'tooltip-popup ds-forced-colors-scope sc-ds-tooltip';\n Object.assign(tip.style, {\n position: 'fixed',\n left: '0',\n top: '0',\n zIndex: 'var(--dimension-z-index-tooltip)',\n pointerEvents: 'none',\n });\n document.body.appendChild(tip);\n this.popupEl = tip;\n }\n\n this.setupOpenListeners();\n this.linkDescribedBy();\n this.popupEl.classList.toggle('tooltip-popup--instant', this.skipEnterAnimation);\n this.popupEl.classList.toggle('tooltip-popup--wrap', this.wrapLabel);\n this.popupEl.classList.remove('tooltip-popup--closing');\n this.renderPopupContent();\n this.schedulePosition();\n this.isOpen = true;\n }\n\n private renderPopupContent() {\n if (!this.popupEl) return;\n this.popupEl.classList.toggle('tooltip-popup--wrap', this.wrapLabel);\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const density =\n this.size === 'md' ? 'ds-control--md' : this.size === 'sm' ? 'ds-control--sm' : 'ds-control--xs';\n // Every layout node needs `sc-ds-tooltip` so scoped selectors (`.foo.sc-ds-tooltip`) match.\n const sc = 'sc-ds-tooltip';\n\n const inner = document.createElement('div');\n inner.className = `tooltip-inner ${density} ${sc}`;\n\n const appendShortcut = (position: 'start' | 'end') => {\n if (!this.shortcutKey || this.shortcutKeyPosition !== position) return;\n const group = document.createElement('span');\n group.className = `key-hint-group ${sc}`;\n group.setAttribute('aria-hidden', 'true');\n\n for (const label of shortcutKeyLabels(this.shortcutKey)) {\n const hint = document.createElement('span');\n const wide = Array.from(label).length > 1 ? ' key-hint--wide' : '';\n hint.className = `key-hint${wide} ${sc}`;\n const text = document.createElement('span');\n text.className = `key-hint__label ${sc}`;\n text.textContent = label;\n hint.appendChild(text);\n group.appendChild(hint);\n }\n inner.appendChild(group);\n };\n\n appendShortcut('start');\n\n // ds-text is the layout box (metric-box + 2px control label-inset).\n // Inline styles required — see styleTooltipLabel.\n const label = createDsText({\n variant: textVariant,\n wrap: this.wrapLabel ? 'wrap' : 'nowrap',\n text: this.label,\n });\n styleTooltipLabel(label, this.wrapLabel);\n inner.appendChild(label);\n\n appendShortcut('end');\n\n this.popupEl.replaceChildren(inner);\n }\n\n render() {\n return (\n <Host style={{ display: 'contents' }}>\n <slot onSlotchange={this.handleSlotChange} />\n </Host>\n );\n }\n}\n",
|
|
304
365
|
"type": "registry:ui"
|
|
305
366
|
}
|
|
306
367
|
]
|
package/dist/styles/table.css
CHANGED
|
@@ -8,17 +8,48 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
:where(.ds-table) {
|
|
11
|
-
--_table-cell-track-min-block-size:
|
|
12
|
-
var(--dimension-size-400) - var(--dimension-space-050)
|
|
13
|
-
);
|
|
11
|
+
--_table-cell-track-min-block-size: var(--dimension-size-300);
|
|
14
12
|
--_table-cell-track-padding-inline: calc(
|
|
15
13
|
var(--dimension-space-075) - var(--dimension-space-025)
|
|
16
14
|
);
|
|
17
15
|
--_table-cell-track-label-inset: var(--dimension-space-025);
|
|
18
16
|
--_table-caption-bar-block-size: var(--dimension-size-600);
|
|
19
|
-
--_table-image-block-size:
|
|
20
|
-
|
|
17
|
+
--_table-image-block-size: var(--_table-cell-track-min-block-size);
|
|
18
|
+
--_table-image-block-size-multi: calc(
|
|
19
|
+
var(--_table-cell-track-min-block-size)
|
|
20
|
+
+ var(--dimension-space-025)
|
|
21
|
+
+ var(--dimension-size-250)
|
|
22
|
+
);
|
|
23
|
+
--_table-image-block-size-triple: calc(
|
|
24
|
+
var(--_table-image-block-size-multi)
|
|
25
|
+
+ var(--dimension-space-025)
|
|
26
|
+
+ var(--dimension-size-250)
|
|
27
|
+
);
|
|
28
|
+
/*
|
|
29
|
+
* Wrapping primary on a 1-track cell occupies later tracks instead of a
|
|
30
|
+
* private leading scale. Keep body-medium size; add the 2px stack gap to
|
|
31
|
+
* line-height and leave the leftover first-track inset as padding-block
|
|
32
|
+
* so 1 / 2 / 3 wrapped lines measure 40 / 62 / 84 like named tracks.
|
|
33
|
+
*/
|
|
34
|
+
--_table-wrap-primary-line-height: calc(
|
|
35
|
+
var(--typography-lineheight-md) + var(--dimension-space-025)
|
|
21
36
|
);
|
|
37
|
+
--_table-wrap-primary-padding-block: calc(
|
|
38
|
+
(
|
|
39
|
+
var(--_table-cell-track-min-block-size)
|
|
40
|
+
- var(--typography-lineheight-md)
|
|
41
|
+
- var(--dimension-space-025)
|
|
42
|
+
) / 2
|
|
43
|
+
);
|
|
44
|
+
/*
|
|
45
|
+
* Wrapping secondary on a 2-track cell occupies later tracks. Zero the copy
|
|
46
|
+
* gap and use later-track + gap as line-height so 1 / 2 / 3 secondary lines
|
|
47
|
+
* land on 62 / 84 / 106 with primary remaining the first 24px track.
|
|
48
|
+
*/
|
|
49
|
+
--_table-wrap-secondary-line-height: calc(
|
|
50
|
+
var(--dimension-size-250) + var(--dimension-space-025)
|
|
51
|
+
);
|
|
52
|
+
--_table-wrap-secondary-padding-block: 0;
|
|
22
53
|
--_table-surface: var(--ds-table-surface, var(--color-background-primary));
|
|
23
54
|
--_table-header-surface: var(--ds-table-header-surface, var(--color-background-primary));
|
|
24
55
|
--_table-group-surface: var(--ds-table-group-surface, var(--color-background-faint-neutral));
|
|
@@ -762,34 +793,47 @@
|
|
|
762
793
|
|
|
763
794
|
:where(.ds-table__cell-track--text) {
|
|
764
795
|
inline-size: 100%;
|
|
765
|
-
padding-inline: var(--_table-cell-track-
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
:where(.ds-table__cell--text-single),
|
|
769
|
-
:where(.ds-table__cell--text-multi),
|
|
770
|
-
:where(.ds-table__cell--primary-text),
|
|
771
|
-
:where(.ds-table__cell--empty),
|
|
772
|
-
:where(.ds-table__cell--blank) {
|
|
773
|
-
padding: var(--dimension-space-125);
|
|
796
|
+
padding-inline: var(--_table-cell-track-padding-inline);
|
|
774
797
|
}
|
|
775
798
|
|
|
776
799
|
:where(.ds-table__cell--text-single .ds-table__cell-content),
|
|
777
800
|
:where(.ds-table__cell--text-multi .ds-table__cell-content),
|
|
801
|
+
:where(.ds-table__cell--text-triple .ds-table__cell-content),
|
|
778
802
|
:where(.ds-table__cell--primary-text .ds-table__cell-content),
|
|
779
803
|
:where(.ds-table__cell--empty .ds-table__cell-content),
|
|
780
804
|
:where(.ds-table__cell--blank .ds-table__cell-content),
|
|
781
805
|
:where(.ds-table__cell--text-single .ds-table__cell-track),
|
|
782
806
|
:where(.ds-table__cell--text-multi .ds-table__cell-track),
|
|
807
|
+
:where(.ds-table__cell--text-triple .ds-table__cell-track),
|
|
783
808
|
:where(.ds-table__cell--primary-text .ds-table__cell-track),
|
|
784
809
|
:where(.ds-table__cell--empty .ds-table__cell-track) {
|
|
785
810
|
min-block-size: 0;
|
|
786
811
|
}
|
|
787
812
|
|
|
788
|
-
:where(.ds-table__cell--text-multi .ds-table__cell-copy)
|
|
789
|
-
|
|
813
|
+
:where(.ds-table__cell--text-multi .ds-table__cell-copy),
|
|
814
|
+
:where(.ds-table__cell--text-triple .ds-table__cell-copy) {
|
|
815
|
+
gap: var(--dimension-space-025);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/*
|
|
819
|
+
* Body cells share 8px chrome. 20px content (body-medium text, md icon,
|
|
820
|
+
* checkbox) sits in a 24px track (2px block inset). Text tracks keep that
|
|
821
|
+
* 2px plus the 2px label inset, so padding-inline is 4px and copy lines up
|
|
822
|
+
* with column-header labels. Later body-small tracks stay 20px, stacked with
|
|
823
|
+
* a 2px gap. Wrapping 1-track primary folds that gap into line-height so
|
|
824
|
+
* extra lines land on the same 62px and 84px rows as 2- and 3-track cells.
|
|
825
|
+
* Wrapping secondary keeps primary on track 1 and consumes later tracks the
|
|
826
|
+
* same way, including a 106px fourth track when secondary wraps to three lines.
|
|
827
|
+
* Header and group rows keep their own recipes.
|
|
828
|
+
*/
|
|
829
|
+
:where(.ds-table__cell-primary),
|
|
830
|
+
:where(.ds-table__cell--empty .ds-table__cell-track) {
|
|
831
|
+
padding-block: var(--dimension-space-025);
|
|
790
832
|
}
|
|
791
833
|
|
|
792
|
-
:where(.ds-table__cell--text-multi .ds-table__cell-secondary)
|
|
834
|
+
:where(.ds-table__cell--text-multi .ds-table__cell-secondary),
|
|
835
|
+
:where(.ds-table__cell--text-triple .ds-table__cell-secondary),
|
|
836
|
+
:where(.ds-table__cell--text-triple .ds-table__cell-tertiary) {
|
|
793
837
|
padding-block: var(--dimension-space-025);
|
|
794
838
|
}
|
|
795
839
|
|
|
@@ -797,13 +841,8 @@
|
|
|
797
841
|
padding-block: 0;
|
|
798
842
|
}
|
|
799
843
|
|
|
800
|
-
:where(.ds-table__cell--tag-tag-only)
|
|
801
|
-
|
|
802
|
-
padding: var(--dimension-space-100);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
:where(.ds-table__cell--tag-text-with-tag) {
|
|
806
|
-
padding: var(--dimension-space-125);
|
|
844
|
+
:where(.ds-table__cell--tag-tag-only .ds-table__cell-content) {
|
|
845
|
+
min-block-size: 0;
|
|
807
846
|
}
|
|
808
847
|
|
|
809
848
|
:where(.ds-table__cell-tag-stack) {
|
|
@@ -830,12 +869,13 @@
|
|
|
830
869
|
}
|
|
831
870
|
|
|
832
871
|
:where(.ds-table__cell-tag-stack--text-with-tag .ds-table__cell-tag-text) {
|
|
833
|
-
padding-inline: var(--_table-cell-track-
|
|
872
|
+
padding-inline: var(--_table-cell-track-padding-inline);
|
|
873
|
+
padding-block: var(--dimension-space-025);
|
|
834
874
|
min-block-size: 0;
|
|
835
875
|
}
|
|
836
876
|
|
|
837
877
|
:where(.ds-table__cell-tag-stack--text-with-tag) {
|
|
838
|
-
gap: var(--dimension-space-
|
|
878
|
+
gap: var(--dimension-space-025);
|
|
839
879
|
}
|
|
840
880
|
|
|
841
881
|
:where(.ds-table__cell-tag-control-track) {
|
|
@@ -848,21 +888,56 @@
|
|
|
848
888
|
min-block-size: var(--dimension-size-250);
|
|
849
889
|
}
|
|
850
890
|
|
|
851
|
-
:where(.ds-table__cell--icon) {
|
|
852
|
-
padding: var(--dimension-space-125);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
891
|
:where(.ds-table__cell--icon .ds-table__cell-content) {
|
|
856
892
|
align-items: center;
|
|
857
893
|
justify-content: center;
|
|
858
894
|
inline-size: 100%;
|
|
859
|
-
min-block-size: var(--dimension-
|
|
895
|
+
min-block-size: var(--dimension-size-300);
|
|
860
896
|
}
|
|
861
897
|
|
|
862
|
-
|
|
863
|
-
|
|
898
|
+
/*
|
|
899
|
+
* Icon-and-text: md prefix in a 24px slot (2px padding on every side), then a
|
|
900
|
+
* 2px flex gap. Text tracks keep their own 4px inline and 2px block padding.
|
|
901
|
+
*/
|
|
902
|
+
:where(.ds-table__cell-icon-text) {
|
|
903
|
+
display: flex;
|
|
904
|
+
align-items: flex-start;
|
|
905
|
+
gap: var(--dimension-space-025);
|
|
906
|
+
inline-size: 100%;
|
|
907
|
+
min-inline-size: 0;
|
|
908
|
+
max-inline-size: 100%;
|
|
864
909
|
}
|
|
865
910
|
|
|
911
|
+
:where(.ds-table__cell-icon-text-icon) {
|
|
912
|
+
box-sizing: border-box;
|
|
913
|
+
display: flex;
|
|
914
|
+
flex: none;
|
|
915
|
+
align-items: flex-start;
|
|
916
|
+
min-block-size: var(--_table-cell-track-min-block-size);
|
|
917
|
+
padding: var(--dimension-space-025);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
:where(.ds-table__cell--icon-text .ds-table__cell-content),
|
|
921
|
+
:where(.ds-table__cell--icon-text .ds-table__cell-track) {
|
|
922
|
+
min-block-size: 0;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
:where(.ds-table__cell--icon-text-multi .ds-table__cell-copy),
|
|
926
|
+
:where(.ds-table__cell--icon-text-triple .ds-table__cell-copy) {
|
|
927
|
+
gap: var(--dimension-space-025);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
:where(.ds-table__cell--icon-text-multi .ds-table__cell-secondary),
|
|
931
|
+
:where(.ds-table__cell--icon-text-triple .ds-table__cell-secondary),
|
|
932
|
+
:where(.ds-table__cell--icon-text-triple .ds-table__cell-tertiary) {
|
|
933
|
+
padding-block: var(--dimension-space-025);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/*
|
|
937
|
+
* Image previews fill the matching body-row content box (cell height minus
|
|
938
|
+
* 8px padding) at 16:9. Track 1 is the 24px first-track stack; tracks 2 and
|
|
939
|
+
* 3 add the same 20px later-track and 2px gap as text.
|
|
940
|
+
*/
|
|
866
941
|
:where(.ds-table__cell-image) {
|
|
867
942
|
display: block;
|
|
868
943
|
box-sizing: border-box;
|
|
@@ -874,6 +949,14 @@
|
|
|
874
949
|
border-radius: var(--dimension-radius-025);
|
|
875
950
|
}
|
|
876
951
|
|
|
952
|
+
:where(.ds-table__cell--image-multi .ds-table__cell-image) {
|
|
953
|
+
block-size: var(--_table-image-block-size-multi);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
:where(.ds-table__cell--image-triple .ds-table__cell-image) {
|
|
957
|
+
block-size: var(--_table-image-block-size-triple);
|
|
958
|
+
}
|
|
959
|
+
|
|
877
960
|
:where(.ds-table__cell-image-content),
|
|
878
961
|
:where(.ds-table__cell-image-placeholder) {
|
|
879
962
|
display: block;
|
|
@@ -885,26 +968,70 @@
|
|
|
885
968
|
object-fit: cover;
|
|
886
969
|
}
|
|
887
970
|
|
|
888
|
-
:where(.ds-table__cell--action) {
|
|
889
|
-
padding: var(--dimension-space-100);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
971
|
:where(.ds-table__cell--action .ds-table__cell-content) {
|
|
893
972
|
min-block-size: var(--dimension-size-300);
|
|
894
973
|
}
|
|
895
974
|
|
|
896
975
|
:where(.ds-table__cell-primary),
|
|
897
|
-
:where(.ds-table__cell-secondary)
|
|
976
|
+
:where(.ds-table__cell-secondary),
|
|
977
|
+
:where(.ds-table__cell-tertiary),
|
|
978
|
+
:where(.ds-table__cell-run),
|
|
979
|
+
:where(.ds-table__cell-link) {
|
|
898
980
|
min-inline-size: 0;
|
|
899
981
|
}
|
|
900
982
|
|
|
983
|
+
:where(.ds-table__cell-track--runs) {
|
|
984
|
+
overflow: hidden;
|
|
985
|
+
gap: var(--dimension-space-050);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
:where(.ds-table__cell-run-separator) {
|
|
989
|
+
flex: none;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
:where(.ds-table__cell-link) {
|
|
993
|
+
display: flex;
|
|
994
|
+
max-inline-size: 100%;
|
|
995
|
+
inline-size: 100%;
|
|
996
|
+
}
|
|
997
|
+
|
|
901
998
|
:where(.ds-table__cell-copy--wrap) {
|
|
902
999
|
overflow-wrap: anywhere;
|
|
903
1000
|
}
|
|
904
1001
|
|
|
1002
|
+
/*
|
|
1003
|
+
* Wrapping tracks must be block boxes so ds-text line-clamp (a -webkit-box
|
|
1004
|
+
* inner) can grow onto later tracks. Flex + line-clamp otherwise collapses
|
|
1005
|
+
* to the 40px single-track row.
|
|
1006
|
+
*/
|
|
1007
|
+
:where(.ds-table__cell--text-wrap .ds-table__cell-track--text),
|
|
1008
|
+
:where(.ds-table__cell--icon-text-wrap .ds-table__cell-track--text) {
|
|
1009
|
+
display: block;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
:where(.ds-table__cell--text-wrap.ds-table__cell--text-single .ds-table__cell-primary),
|
|
1013
|
+
:where(.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-primary),
|
|
1014
|
+
:where(.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-single .ds-table__cell-primary),
|
|
1015
|
+
:where(.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-primary) {
|
|
1016
|
+
line-height: var(--_table-wrap-primary-line-height);
|
|
1017
|
+
padding-block: var(--_table-wrap-primary-padding-block);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
:where(.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-copy),
|
|
1021
|
+
:where(.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-copy) {
|
|
1022
|
+
gap: 0;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
:where(.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-secondary),
|
|
1026
|
+
:where(.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-secondary) {
|
|
1027
|
+
line-height: var(--_table-wrap-secondary-line-height);
|
|
1028
|
+
padding-block: var(--_table-wrap-secondary-padding-block);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
905
1031
|
:where(.ds-table__cell--align-center),
|
|
906
1032
|
:where(.ds-table__cell--align-center .ds-table__cell-content),
|
|
907
1033
|
:where(.ds-table__cell--align-center .ds-table__cell-track),
|
|
1034
|
+
:where(.ds-table__cell--align-center .ds-table__cell-link),
|
|
908
1035
|
:where(.ds-table__cell--align-center .ds-table__header-static),
|
|
909
1036
|
:where(.ds-table__cell--align-center .ds-table__header-labels),
|
|
910
1037
|
:where(.ds-table__cell--align-center .ds-table__header-label) {
|
|
@@ -915,6 +1042,7 @@
|
|
|
915
1042
|
:where(.ds-table__cell--align-end),
|
|
916
1043
|
:where(.ds-table__cell--align-end .ds-table__cell-content),
|
|
917
1044
|
:where(.ds-table__cell--align-end .ds-table__cell-track),
|
|
1045
|
+
:where(.ds-table__cell--align-end .ds-table__cell-link),
|
|
918
1046
|
:where(.ds-table__cell--align-end .ds-table__header-static),
|
|
919
1047
|
:where(.ds-table__cell--align-end .ds-table__header-labels),
|
|
920
1048
|
:where(.ds-table__cell--align-end .ds-table__header-label) {
|
|
@@ -1054,7 +1182,7 @@
|
|
|
1054
1182
|
}
|
|
1055
1183
|
|
|
1056
1184
|
:where(.ds-table__cell.ds-table__selection-cell) {
|
|
1057
|
-
padding: var(--
|
|
1185
|
+
padding: var(--_table-cell-padding-block) var(--_table-cell-padding-inline);
|
|
1058
1186
|
}
|
|
1059
1187
|
|
|
1060
1188
|
:where(.ds-table__selection-control) {
|
|
@@ -1077,9 +1205,9 @@
|
|
|
1077
1205
|
}
|
|
1078
1206
|
|
|
1079
1207
|
:where(.ds-table__cell.ds-table__selection-cell .ds-table__selection-control) {
|
|
1080
|
-
inline-size: var(--dimension-
|
|
1081
|
-
min-block-size: var(--dimension-
|
|
1082
|
-
block-size: var(--dimension-
|
|
1208
|
+
inline-size: var(--dimension-size-300);
|
|
1209
|
+
min-block-size: var(--dimension-size-300);
|
|
1210
|
+
block-size: var(--dimension-size-300);
|
|
1083
1211
|
}
|
|
1084
1212
|
|
|
1085
1213
|
:where(.ds-table__selection-control:disabled) {
|
|
@@ -46,6 +46,7 @@ export declare class Menu {
|
|
|
46
46
|
dsSwatchSelect: EventEmitter<string>;
|
|
47
47
|
private clickOutsideHandler;
|
|
48
48
|
private closeTimer;
|
|
49
|
+
private positionReadyCallback;
|
|
49
50
|
/**
|
|
50
51
|
* Anchored placement. The choice-cell measurement stays here on purpose: the
|
|
51
52
|
* inner-cell align offset and the `--dimension-menu-width-xs` floor are Menu
|
|
@@ -99,6 +99,13 @@ export declare class Table {
|
|
|
99
99
|
private viewportFitSettled;
|
|
100
100
|
private headerPresent;
|
|
101
101
|
private fitPageSize;
|
|
102
|
+
private actionMenu;
|
|
103
|
+
private actionMenuInitialFocusVisible;
|
|
104
|
+
private truncateTooltipLabel;
|
|
105
|
+
private readonly actionMenuElementId;
|
|
106
|
+
private truncateTooltipEl?;
|
|
107
|
+
private truncateAnchor;
|
|
108
|
+
private truncateTooltipBound;
|
|
102
109
|
private rootEl;
|
|
103
110
|
private viewportEl;
|
|
104
111
|
private frameEl;
|
|
@@ -166,6 +173,21 @@ export declare class Table {
|
|
|
166
173
|
private emitGroupSelection;
|
|
167
174
|
private rowEventOwnsActivation;
|
|
168
175
|
private emitRowActivation;
|
|
176
|
+
private syncActionMenu;
|
|
177
|
+
private closeActionMenu;
|
|
178
|
+
private handleActionMenuSelect;
|
|
179
|
+
private toggleActionMenu;
|
|
180
|
+
private renderOverflowActionMenu;
|
|
181
|
+
private connectTruncateTooltip;
|
|
182
|
+
private disconnectTruncateTooltip;
|
|
183
|
+
private onTruncatePointerOver;
|
|
184
|
+
private onTruncatePointerOut;
|
|
185
|
+
private onTruncateFocusIn;
|
|
186
|
+
private onTruncateFocusOut;
|
|
187
|
+
private presentOrDismissTruncateTrack;
|
|
188
|
+
private dismissTruncateTooltip;
|
|
189
|
+
private renderTruncateTooltip;
|
|
190
|
+
private truncateAttr;
|
|
169
191
|
private handleRowKeydown;
|
|
170
192
|
private renderSelectionControl;
|
|
171
193
|
private renderStickyEdge;
|
|
@@ -175,6 +197,8 @@ export declare class Table {
|
|
|
175
197
|
private renderColgroup;
|
|
176
198
|
private renderHeader;
|
|
177
199
|
private renderCellValue;
|
|
200
|
+
private renderTextCopy;
|
|
201
|
+
private renderTextTrack;
|
|
178
202
|
private renderRow;
|
|
179
203
|
private emitGroupCollapse;
|
|
180
204
|
private emitAllGroupsCollapse;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { TableCaptionVisibility, TableCellAlign, TableCellAction, TableCellActionDetail, TableCellBlank, TableCellEmpty, TableCellIcon, TableCellImage, TableCellPrimaryText, TableCellSkeleton, TableCellText, TableCellTag, TableCellTagVariant, TableCellValue, TableSkeletonWidth, TableColumn, TableColumnSticky, TableColumnWidth, TableDataMode, TableGroup, TableGroupCollapseChangeDetail, TableGroupLoadMoreDetail, TableGroupIntent, TableHeaderSegment, TableGroupingState, TableLoadMoreDetail, TableLoadMoreMode, TableLoadMoreReason, TablePaginationState, TableRow, TableRowActivateDetail, TableSelectionChangeDetail, TableSelectionMode, TableSortChangeDetail, TableSortDirection, TableSortState, } from './table-types';
|
|
1
|
+
export type { TableCaptionVisibility, TableCellAlign, TableCellAction, TableCellActionDetail, TableCellActionDivider, TableCellActionMenuEntry, TableCellActionMenuItem, TableCellBlank, TableCellEmpty, TableCellLinkTarget, TableCellIcon, TableCellIconText, TableCellImage, TableCellImageTracks, TableCellPrimaryText, TableCellSkeleton, TableCellText, TableCellTextRun, TableCellTextTrack, TableCellTag, TableCellTagVariant, TableCellValue, TableSkeletonWidth, TableColumn, TableColumnSticky, TableColumnWidth, TableDataMode, TableGroup, TableGroupCollapseChangeDetail, TableGroupLoadMoreDetail, TableGroupIntent, TableHeaderSegment, TableGroupingState, TableLoadMoreDetail, TableLoadMoreMode, TableLoadMoreReason, TablePaginationState, TableRow, TableRowActivateDetail, TableSelectionChangeDetail, TableSelectionMode, TableSortChangeDetail, TableSortDirection, TableSortState, } from './table-types';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MenuItemsSection } from '../Menu/menu-types';
|
|
2
|
+
import type { TableCellAction, TableCellActionMenuEntry } from './table-types';
|
|
3
|
+
export declare function nextTableActionMenuElementId(): string;
|
|
4
|
+
export declare function isTableCellActionMenu(value: TableCellAction): value is TableCellAction & {
|
|
5
|
+
items: TableCellActionMenuEntry[];
|
|
6
|
+
ariaLabel: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function isRenderableTableActionMenu(value: TableCellAction): value is TableCellAction & {
|
|
9
|
+
items: TableCellActionMenuEntry[];
|
|
10
|
+
ariaLabel: string;
|
|
11
|
+
};
|
|
12
|
+
/** Split divider entries into Menu sections. Empty runs (leading/repeated dividers) are dropped. */
|
|
13
|
+
export declare function tableActionMenuSections(items: TableCellActionMenuEntry[]): MenuItemsSection[];
|
|
14
|
+
export declare function tableActionTriggerId(menuId: string, rowId: string, columnId: string): string;
|
|
15
|
+
//# sourceMappingURL=table-action-menu.d.ts.map
|