@spectrum-web-components/overlay 1.12.0-testing.20260223092154 → 1.12.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/custom-elements.json +71 -0
- package/package.json +6 -6
- package/src/HoverController.dev.js +4 -0
- package/src/HoverController.dev.js.map +2 -2
- package/src/HoverController.js +1 -1
- package/src/HoverController.js.map +2 -2
- package/src/LongpressController.dev.js +4 -0
- package/src/LongpressController.dev.js.map +2 -2
- package/src/LongpressController.js +1 -1
- package/src/LongpressController.js.map +2 -2
- package/src/Overlay.d.ts +11 -0
- package/src/Overlay.dev.js +8 -3
- package/src/Overlay.dev.js.map +2 -2
- package/src/Overlay.js +2 -2
- package/src/Overlay.js.map +3 -3
- package/src/OverlayTrigger.d.ts +11 -0
- package/src/OverlayTrigger.dev.js +95 -16
- package/src/OverlayTrigger.dev.js.map +2 -2
- package/src/OverlayTrigger.js +6 -6
- package/src/OverlayTrigger.js.map +3 -3
- package/src/PlacementController.d.ts +9 -0
- package/src/PlacementController.dev.js +72 -3
- package/src/PlacementController.dev.js.map +3 -3
- package/src/PlacementController.js +1 -1
- package/src/PlacementController.js.map +3 -3
package/custom-elements.json
CHANGED
|
@@ -3046,6 +3046,21 @@
|
|
|
3046
3046
|
"text": "Overlay"
|
|
3047
3047
|
}
|
|
3048
3048
|
},
|
|
3049
|
+
{
|
|
3050
|
+
"kind": "field",
|
|
3051
|
+
"name": "ariaManagedElements",
|
|
3052
|
+
"privacy": "private",
|
|
3053
|
+
"default": "new WeakSet<HTMLElement>()",
|
|
3054
|
+
"description": "Tracks elements where this component has taken ownership\nof ARIA attributes, so consumer-set values are never removed."
|
|
3055
|
+
},
|
|
3056
|
+
{
|
|
3057
|
+
"kind": "field",
|
|
3058
|
+
"name": "previousTriggerElement",
|
|
3059
|
+
"type": {
|
|
3060
|
+
"text": "HTMLElement | undefined"
|
|
3061
|
+
},
|
|
3062
|
+
"privacy": "private"
|
|
3063
|
+
},
|
|
3049
3064
|
{
|
|
3050
3065
|
"kind": "method",
|
|
3051
3066
|
"name": "getAssignedElementsFromSlot",
|
|
@@ -3118,6 +3133,52 @@
|
|
|
3118
3133
|
}
|
|
3119
3134
|
]
|
|
3120
3135
|
},
|
|
3136
|
+
{
|
|
3137
|
+
"kind": "field",
|
|
3138
|
+
"name": "VALID_HASPOPUP_ROLES",
|
|
3139
|
+
"privacy": "private",
|
|
3140
|
+
"static": true,
|
|
3141
|
+
"readonly": true,
|
|
3142
|
+
"default": "new Set([ 'menu', 'listbox', 'tree', 'grid', 'dialog', ])"
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
"kind": "method",
|
|
3146
|
+
"name": "resolveHaspopupValue",
|
|
3147
|
+
"privacy": "private",
|
|
3148
|
+
"return": {
|
|
3149
|
+
"type": {
|
|
3150
|
+
"text": "string"
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
"kind": "method",
|
|
3156
|
+
"name": "removeAriaFromTrigger",
|
|
3157
|
+
"privacy": "private",
|
|
3158
|
+
"return": {
|
|
3159
|
+
"type": {
|
|
3160
|
+
"text": "void"
|
|
3161
|
+
}
|
|
3162
|
+
},
|
|
3163
|
+
"parameters": [
|
|
3164
|
+
{
|
|
3165
|
+
"name": "element",
|
|
3166
|
+
"type": {
|
|
3167
|
+
"text": "HTMLElement"
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
]
|
|
3171
|
+
},
|
|
3172
|
+
{
|
|
3173
|
+
"kind": "method",
|
|
3174
|
+
"name": "manageAriaOnTrigger",
|
|
3175
|
+
"privacy": "private",
|
|
3176
|
+
"return": {
|
|
3177
|
+
"type": {
|
|
3178
|
+
"text": "void"
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
},
|
|
3121
3182
|
{
|
|
3122
3183
|
"kind": "method",
|
|
3123
3184
|
"name": "renderSlot",
|
|
@@ -3343,6 +3404,16 @@
|
|
|
3343
3404
|
"privacy": "private",
|
|
3344
3405
|
"description": "The target element for the overlay."
|
|
3345
3406
|
},
|
|
3407
|
+
{
|
|
3408
|
+
"kind": "field",
|
|
3409
|
+
"name": "placementGeneration",
|
|
3410
|
+
"type": {
|
|
3411
|
+
"text": "number"
|
|
3412
|
+
},
|
|
3413
|
+
"privacy": "private",
|
|
3414
|
+
"default": "0",
|
|
3415
|
+
"description": "Incremented whenever the active placement session ends (`cleanup`) or a\nnew session begins, so async `computePlacement` runs that started under an\nolder session do not write styles after teardown or across stacked\n`placeOverlay` calls."
|
|
3416
|
+
},
|
|
3346
3417
|
{
|
|
3347
3418
|
"kind": "method",
|
|
3348
3419
|
"name": "placeOverlay",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectrum-web-components/overlay",
|
|
3
|
-
"version": "1.12.0
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Adobe",
|
|
@@ -160,11 +160,11 @@
|
|
|
160
160
|
"dependencies": {
|
|
161
161
|
"@floating-ui/dom": "1.7.4",
|
|
162
162
|
"@floating-ui/utils": "0.2.10",
|
|
163
|
-
"@spectrum-web-components/action-button": "1.12.0
|
|
164
|
-
"@spectrum-web-components/base": "1.12.0
|
|
165
|
-
"@spectrum-web-components/reactive-controllers": "1.12.0
|
|
166
|
-
"@spectrum-web-components/shared": "1.12.0
|
|
167
|
-
"@spectrum-web-components/theme": "1.12.0
|
|
163
|
+
"@spectrum-web-components/action-button": "1.12.0",
|
|
164
|
+
"@spectrum-web-components/base": "1.12.0",
|
|
165
|
+
"@spectrum-web-components/reactive-controllers": "1.12.0",
|
|
166
|
+
"@spectrum-web-components/shared": "1.12.0",
|
|
167
|
+
"@spectrum-web-components/theme": "1.12.0",
|
|
168
168
|
"focus-trap": "7.6.5"
|
|
169
169
|
},
|
|
170
170
|
"keywords": [
|
|
@@ -91,6 +91,10 @@ export class HoverController extends InteractionController {
|
|
|
91
91
|
if (!this.overlay || !this.overlay.elements.length) {
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
|
+
const overlay = this.overlay;
|
|
95
|
+
if (overlay.describeTrigger === "none") {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
94
98
|
const triggerRoot = this.target.getRootNode();
|
|
95
99
|
const contentRoot = this.overlay.elements[0].getRootNode();
|
|
96
100
|
const overlayRoot = this.overlay.getRootNode();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["HoverController.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.dev.js'\nimport {\n InteractionController,\n InteractionTypes,\n lastInteractionType,\n} from './InteractionController.dev.js'\n\nexport class HoverController extends InteractionController {\n override type = InteractionTypes.hover;\n\n private elementIds: string[] = [];\n\n private targetFocused = false;\n\n private hoverTimeout?: ReturnType<typeof setTimeout>;\n\n private hovering = false;\n\n private overlayFocused = false;\n\n handleKeyup(event: KeyboardEvent): void {\n if (event.code === 'Tab') {\n this.open = true;\n } else if (event.code === 'Escape') {\n if (this.open) {\n event.preventDefault();\n event.stopPropagation();\n this.open = false;\n // Return focus to trigger element\n if (this.target) {\n this.target.focus();\n }\n }\n }\n }\n\n handleTargetFocusin(): void {\n if (!this.target.matches(':focus-visible')) {\n return;\n }\n\n // Don't open hover overlay if the last interaction was a click.\n // This prevents hover from opening when focus returns to trigger\n // after closing a modal overlay via Escape key.\n // Previously this check was WebKit-only, but Chromium also needs it\n // because it reports :focus-visible=true for programmatic focus after keyboard events.\n if (this.target[lastInteractionType] === InteractionTypes.click) {\n return;\n }\n\n this.open = true;\n this.targetFocused = true;\n }\n\n handleTargetFocusout(): void {\n this.targetFocused = false;\n // Don't close immediately if pointer is over the content\n if (this.hovering) {\n return;\n }\n // Use delay to allow focus to move into overlay content\n this.doFocusleave();\n }\n\n private clearCloseTimeout(): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = undefined;\n }\n }\n\n handleTargetPointerenter(): void {\n this.clearCloseTimeout();\n if (this.overlay?.disabled) {\n return;\n }\n this.open = true;\n this.hovering = true;\n }\n\n handleTargetPointerleave(): void {\n this.doPointerleave();\n }\n\n // set a timeout once the pointer enters and the overlay is shown\n // give the user time to enter the overlay\n handleHostPointerenter(): void {\n this.clearCloseTimeout();\n }\n\n handleHostPointerleave(): void {\n this.doPointerleave();\n }\n\n handleOverlayFocusin(): void {\n this.overlayFocused = true;\n // Clear any pending close timeout when focus enters overlay\n this.clearCloseTimeout();\n }\n\n handleOverlayFocusout(): void {\n this.overlayFocused = false;\n // Don't close immediately if pointer is over the content or trigger has focus\n if (this.hovering) {\n return;\n }\n if (this.targetFocused && this.target.matches(':focus-visible')) {\n return;\n }\n // Use delay before closing\n this.doFocusleave();\n }\n\n override prepareDescription(): void {\n // require \"content\" to apply relationship\n if (!this.overlay || !this.overlay.elements.length) {\n return;\n }\n\n const triggerRoot = this.target.getRootNode();\n const contentRoot = this.overlay.elements[0].getRootNode();\n const overlayRoot = this.overlay.getRootNode();\n if (triggerRoot === overlayRoot) {\n this.prepareOverlayRelativeDescription();\n } else if (triggerRoot === contentRoot) {\n this.prepareContentRelativeDescription();\n }\n }\n\n private prepareOverlayRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n [this.overlay.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n this.releaseDescription = noop;\n };\n }\n\n private prepareContentRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const overlay = this.overlay; // Capture for closure\n const elementIds: string[] = [];\n const appliedIds = overlay.elements.map((el) => {\n elementIds.push(el.id);\n if (!el.id) {\n el.id = `${overlay.tagName.toLowerCase()}-helper-${randomID()}`;\n }\n return el.id;\n });\n this.elementIds = elementIds;\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n appliedIds\n );\n this.releaseDescription = () => {\n releaseDescription();\n overlay.elements.map((el, index) => {\n el.id = this.elementIds[index];\n });\n this.releaseDescription = noop;\n };\n }\n\n private scheduleClose(): void {\n this.hoverTimeout = setTimeout(() => {\n this.open = false;\n }, 300);\n }\n\n private doPointerleave(): void {\n this.hovering = false;\n const triggerElement = this.target as HTMLElement;\n if (this.targetFocused && triggerElement.matches(':focus-visible')) {\n return;\n }\n // Don't close if focus is within overlay content\n if (this.overlayFocused) {\n return;\n }\n\n this.scheduleClose();\n }\n\n private doFocusleave(): void {\n // Clear any existing timeout\n this.clearCloseTimeout();\n\n // Use same delay as pointer interactions for consistency\n if (!this.targetFocused && !this.overlayFocused && !this.hovering) {\n this.scheduleClose();\n }\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n this.target.addEventListener('focusin', () => this.handleTargetFocusin(), {\n signal,\n });\n this.target.addEventListener(\n 'focusout',\n () => this.handleTargetFocusout(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerenter',\n () => this.handleTargetPointerenter(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerleave',\n () => this.handleTargetPointerleave(),\n { signal }\n );\n if (this.overlay) {\n this.initOverlay();\n }\n }\n\n override initOverlay(): void {\n if (!this.abortController || !this.overlay) {\n return;\n }\n const { signal } = this.abortController;\n this.overlay.addEventListener(\n 'pointerenter',\n () => this.handleHostPointerenter(),\n { signal }\n );\n this.overlay.addEventListener(\n 'pointerleave',\n () => this.handleHostPointerleave(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusin',\n () => this.handleOverlayFocusin(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusout',\n () => this.handleOverlayFocusout(),\n { signal }\n );\n this.overlay.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAYA,SAAS,gCAAgC;AACzC,SAAS,gBAAgB;AAEzB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.dev.js'\nimport {\n InteractionController,\n InteractionTypes,\n lastInteractionType,\n} from './InteractionController.dev.js'\nimport type { Overlay } from './Overlay.dev.js'\n\nexport class HoverController extends InteractionController {\n override type = InteractionTypes.hover;\n\n private elementIds: string[] = [];\n\n private targetFocused = false;\n\n private hoverTimeout?: ReturnType<typeof setTimeout>;\n\n private hovering = false;\n\n private overlayFocused = false;\n\n handleKeyup(event: KeyboardEvent): void {\n if (event.code === 'Tab') {\n this.open = true;\n } else if (event.code === 'Escape') {\n if (this.open) {\n event.preventDefault();\n event.stopPropagation();\n this.open = false;\n // Return focus to trigger element\n if (this.target) {\n this.target.focus();\n }\n }\n }\n }\n\n handleTargetFocusin(): void {\n if (!this.target.matches(':focus-visible')) {\n return;\n }\n\n // Don't open hover overlay if the last interaction was a click.\n // This prevents hover from opening when focus returns to trigger\n // after closing a modal overlay via Escape key.\n // Previously this check was WebKit-only, but Chromium also needs it\n // because it reports :focus-visible=true for programmatic focus after keyboard events.\n if (this.target[lastInteractionType] === InteractionTypes.click) {\n return;\n }\n\n this.open = true;\n this.targetFocused = true;\n }\n\n handleTargetFocusout(): void {\n this.targetFocused = false;\n // Don't close immediately if pointer is over the content\n if (this.hovering) {\n return;\n }\n // Use delay to allow focus to move into overlay content\n this.doFocusleave();\n }\n\n private clearCloseTimeout(): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = undefined;\n }\n }\n\n handleTargetPointerenter(): void {\n this.clearCloseTimeout();\n if (this.overlay?.disabled) {\n return;\n }\n this.open = true;\n this.hovering = true;\n }\n\n handleTargetPointerleave(): void {\n this.doPointerleave();\n }\n\n // set a timeout once the pointer enters and the overlay is shown\n // give the user time to enter the overlay\n handleHostPointerenter(): void {\n this.clearCloseTimeout();\n }\n\n handleHostPointerleave(): void {\n this.doPointerleave();\n }\n\n handleOverlayFocusin(): void {\n this.overlayFocused = true;\n // Clear any pending close timeout when focus enters overlay\n this.clearCloseTimeout();\n }\n\n handleOverlayFocusout(): void {\n this.overlayFocused = false;\n // Don't close immediately if pointer is over the content or trigger has focus\n if (this.hovering) {\n return;\n }\n if (this.targetFocused && this.target.matches(':focus-visible')) {\n return;\n }\n // Use delay before closing\n this.doFocusleave();\n }\n\n override prepareDescription(): void {\n // require \"content\" to apply relationship\n if (!this.overlay || !this.overlay.elements.length) {\n return;\n }\n // When describeTrigger is 'none', do not set aria-describedby on the trigger (visual hint only).\n const overlay = this.overlay as Overlay;\n if (overlay.describeTrigger === 'none') {\n return;\n }\n\n const triggerRoot = this.target.getRootNode();\n const contentRoot = this.overlay.elements[0].getRootNode();\n const overlayRoot = this.overlay.getRootNode();\n if (triggerRoot === overlayRoot) {\n this.prepareOverlayRelativeDescription();\n } else if (triggerRoot === contentRoot) {\n this.prepareContentRelativeDescription();\n }\n }\n\n private prepareOverlayRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n [this.overlay.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n this.releaseDescription = noop;\n };\n }\n\n private prepareContentRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const overlay = this.overlay; // Capture for closure\n const elementIds: string[] = [];\n const appliedIds = overlay.elements.map((el) => {\n elementIds.push(el.id);\n if (!el.id) {\n el.id = `${overlay.tagName.toLowerCase()}-helper-${randomID()}`;\n }\n return el.id;\n });\n this.elementIds = elementIds;\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n appliedIds\n );\n this.releaseDescription = () => {\n releaseDescription();\n overlay.elements.map((el, index) => {\n el.id = this.elementIds[index];\n });\n this.releaseDescription = noop;\n };\n }\n\n private scheduleClose(): void {\n this.hoverTimeout = setTimeout(() => {\n this.open = false;\n }, 300);\n }\n\n private doPointerleave(): void {\n this.hovering = false;\n const triggerElement = this.target as HTMLElement;\n if (this.targetFocused && triggerElement.matches(':focus-visible')) {\n return;\n }\n // Don't close if focus is within overlay content\n if (this.overlayFocused) {\n return;\n }\n\n this.scheduleClose();\n }\n\n private doFocusleave(): void {\n // Clear any existing timeout\n this.clearCloseTimeout();\n\n // Use same delay as pointer interactions for consistency\n if (!this.targetFocused && !this.overlayFocused && !this.hovering) {\n this.scheduleClose();\n }\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n this.target.addEventListener('focusin', () => this.handleTargetFocusin(), {\n signal,\n });\n this.target.addEventListener(\n 'focusout',\n () => this.handleTargetFocusout(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerenter',\n () => this.handleTargetPointerenter(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerleave',\n () => this.handleTargetPointerleave(),\n { signal }\n );\n if (this.overlay) {\n this.initOverlay();\n }\n }\n\n override initOverlay(): void {\n if (!this.abortController || !this.overlay) {\n return;\n }\n const { signal } = this.abortController;\n this.overlay.addEventListener(\n 'pointerenter',\n () => this.handleHostPointerenter(),\n { signal }\n );\n this.overlay.addEventListener(\n 'pointerleave',\n () => this.handleHostPointerleave(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusin',\n () => this.handleOverlayFocusin(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusout',\n () => this.handleOverlayFocusout(),\n { signal }\n );\n this.overlay.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/src/HoverController.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";import{conditionAttributeWithId as
|
|
1
|
+
"use strict";import{conditionAttributeWithId as s}from"@spectrum-web-components/base/src/condition-attribute-with-id.js";import{randomID as h}from"@spectrum-web-components/shared/src/random-id.js";import{noop as a}from"./AbstractOverlay.js";import{InteractionController as d,InteractionTypes as n,lastInteractionType as v}from"./InteractionController.js";export class HoverController extends d{constructor(){super(...arguments);this.type=n.hover;this.elementIds=[];this.targetFocused=!1;this.hovering=!1;this.overlayFocused=!1}handleKeyup(e){e.code==="Tab"?this.open=!0:e.code==="Escape"&&this.open&&(e.preventDefault(),e.stopPropagation(),this.open=!1,this.target&&this.target.focus())}handleTargetFocusin(){this.target.matches(":focus-visible")&&this.target[v]!==n.click&&(this.open=!0,this.targetFocused=!0)}handleTargetFocusout(){this.targetFocused=!1,!this.hovering&&this.doFocusleave()}clearCloseTimeout(){this.hoverTimeout&&(clearTimeout(this.hoverTimeout),this.hoverTimeout=void 0)}handleTargetPointerenter(){var e;this.clearCloseTimeout(),!((e=this.overlay)!=null&&e.disabled)&&(this.open=!0,this.hovering=!0)}handleTargetPointerleave(){this.doPointerleave()}handleHostPointerenter(){this.clearCloseTimeout()}handleHostPointerleave(){this.doPointerleave()}handleOverlayFocusin(){this.overlayFocused=!0,this.clearCloseTimeout()}handleOverlayFocusout(){this.overlayFocused=!1,!this.hovering&&(this.targetFocused&&this.target.matches(":focus-visible")||this.doFocusleave())}prepareDescription(){if(!this.overlay||!this.overlay.elements.length||this.overlay.describeTrigger==="none")return;const t=this.target.getRootNode(),r=this.overlay.elements[0].getRootNode(),o=this.overlay.getRootNode();t===o?this.prepareOverlayRelativeDescription():t===r&&this.prepareContentRelativeDescription()}prepareOverlayRelativeDescription(){if(!this.overlay)return;const e=s(this.target,"aria-describedby",[this.overlay.id]);this.releaseDescription=()=>{e(),this.releaseDescription=a}}prepareContentRelativeDescription(){if(!this.overlay)return;const e=this.overlay,t=[],r=e.elements.map(i=>(t.push(i.id),i.id||(i.id=`${e.tagName.toLowerCase()}-helper-${h()}`),i.id));this.elementIds=t;const o=s(this.target,"aria-describedby",r);this.releaseDescription=()=>{o(),e.elements.map((i,l)=>{i.id=this.elementIds[l]}),this.releaseDescription=a}}scheduleClose(){this.hoverTimeout=setTimeout(()=>{this.open=!1},300)}doPointerleave(){this.hovering=!1;const e=this.target;this.targetFocused&&e.matches(":focus-visible")||this.overlayFocused||this.scheduleClose()}doFocusleave(){this.clearCloseTimeout(),!this.targetFocused&&!this.overlayFocused&&!this.hovering&&this.scheduleClose()}init(){var t;(t=this.abortController)==null||t.abort(),this.abortController=new AbortController;const{signal:e}=this.abortController;this.target.addEventListener("keyup",r=>this.handleKeyup(r),{signal:e}),this.target.addEventListener("focusin",()=>this.handleTargetFocusin(),{signal:e}),this.target.addEventListener("focusout",()=>this.handleTargetFocusout(),{signal:e}),this.target.addEventListener("pointerenter",()=>this.handleTargetPointerenter(),{signal:e}),this.target.addEventListener("pointerleave",()=>this.handleTargetPointerleave(),{signal:e}),this.overlay&&this.initOverlay()}initOverlay(){if(!this.abortController||!this.overlay)return;const{signal:e}=this.abortController;this.overlay.addEventListener("pointerenter",()=>this.handleHostPointerenter(),{signal:e}),this.overlay.addEventListener("pointerleave",()=>this.handleHostPointerleave(),{signal:e}),this.overlay.addEventListener("focusin",()=>this.handleOverlayFocusin(),{signal:e}),this.overlay.addEventListener("focusout",()=>this.handleOverlayFocusout(),{signal:e}),this.overlay.addEventListener("keyup",t=>this.handleKeyup(t),{signal:e})}}
|
|
2
2
|
//# sourceMappingURL=HoverController.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["HoverController.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.js';\nimport {\n InteractionController,\n InteractionTypes,\n lastInteractionType,\n} from './InteractionController.js';\n\nexport class HoverController extends InteractionController {\n override type = InteractionTypes.hover;\n\n private elementIds: string[] = [];\n\n private targetFocused = false;\n\n private hoverTimeout?: ReturnType<typeof setTimeout>;\n\n private hovering = false;\n\n private overlayFocused = false;\n\n handleKeyup(event: KeyboardEvent): void {\n if (event.code === 'Tab') {\n this.open = true;\n } else if (event.code === 'Escape') {\n if (this.open) {\n event.preventDefault();\n event.stopPropagation();\n this.open = false;\n // Return focus to trigger element\n if (this.target) {\n this.target.focus();\n }\n }\n }\n }\n\n handleTargetFocusin(): void {\n if (!this.target.matches(':focus-visible')) {\n return;\n }\n\n // Don't open hover overlay if the last interaction was a click.\n // This prevents hover from opening when focus returns to trigger\n // after closing a modal overlay via Escape key.\n // Previously this check was WebKit-only, but Chromium also needs it\n // because it reports :focus-visible=true for programmatic focus after keyboard events.\n if (this.target[lastInteractionType] === InteractionTypes.click) {\n return;\n }\n\n this.open = true;\n this.targetFocused = true;\n }\n\n handleTargetFocusout(): void {\n this.targetFocused = false;\n // Don't close immediately if pointer is over the content\n if (this.hovering) {\n return;\n }\n // Use delay to allow focus to move into overlay content\n this.doFocusleave();\n }\n\n private clearCloseTimeout(): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = undefined;\n }\n }\n\n handleTargetPointerenter(): void {\n this.clearCloseTimeout();\n if (this.overlay?.disabled) {\n return;\n }\n this.open = true;\n this.hovering = true;\n }\n\n handleTargetPointerleave(): void {\n this.doPointerleave();\n }\n\n // set a timeout once the pointer enters and the overlay is shown\n // give the user time to enter the overlay\n handleHostPointerenter(): void {\n this.clearCloseTimeout();\n }\n\n handleHostPointerleave(): void {\n this.doPointerleave();\n }\n\n handleOverlayFocusin(): void {\n this.overlayFocused = true;\n // Clear any pending close timeout when focus enters overlay\n this.clearCloseTimeout();\n }\n\n handleOverlayFocusout(): void {\n this.overlayFocused = false;\n // Don't close immediately if pointer is over the content or trigger has focus\n if (this.hovering) {\n return;\n }\n if (this.targetFocused && this.target.matches(':focus-visible')) {\n return;\n }\n // Use delay before closing\n this.doFocusleave();\n }\n\n override prepareDescription(): void {\n // require \"content\" to apply relationship\n if (!this.overlay || !this.overlay.elements.length) {\n return;\n }\n\n const triggerRoot = this.target.getRootNode();\n const contentRoot = this.overlay.elements[0].getRootNode();\n const overlayRoot = this.overlay.getRootNode();\n if (triggerRoot === overlayRoot) {\n this.prepareOverlayRelativeDescription();\n } else if (triggerRoot === contentRoot) {\n this.prepareContentRelativeDescription();\n }\n }\n\n private prepareOverlayRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n [this.overlay.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n this.releaseDescription = noop;\n };\n }\n\n private prepareContentRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const overlay = this.overlay; // Capture for closure\n const elementIds: string[] = [];\n const appliedIds = overlay.elements.map((el) => {\n elementIds.push(el.id);\n if (!el.id) {\n el.id = `${overlay.tagName.toLowerCase()}-helper-${randomID()}`;\n }\n return el.id;\n });\n this.elementIds = elementIds;\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n appliedIds\n );\n this.releaseDescription = () => {\n releaseDescription();\n overlay.elements.map((el, index) => {\n el.id = this.elementIds[index];\n });\n this.releaseDescription = noop;\n };\n }\n\n private scheduleClose(): void {\n this.hoverTimeout = setTimeout(() => {\n this.open = false;\n }, 300);\n }\n\n private doPointerleave(): void {\n this.hovering = false;\n const triggerElement = this.target as HTMLElement;\n if (this.targetFocused && triggerElement.matches(':focus-visible')) {\n return;\n }\n // Don't close if focus is within overlay content\n if (this.overlayFocused) {\n return;\n }\n\n this.scheduleClose();\n }\n\n private doFocusleave(): void {\n // Clear any existing timeout\n this.clearCloseTimeout();\n\n // Use same delay as pointer interactions for consistency\n if (!this.targetFocused && !this.overlayFocused && !this.hovering) {\n this.scheduleClose();\n }\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n this.target.addEventListener('focusin', () => this.handleTargetFocusin(), {\n signal,\n });\n this.target.addEventListener(\n 'focusout',\n () => this.handleTargetFocusout(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerenter',\n () => this.handleTargetPointerenter(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerleave',\n () => this.handleTargetPointerleave(),\n { signal }\n );\n if (this.overlay) {\n this.initOverlay();\n }\n }\n\n override initOverlay(): void {\n if (!this.abortController || !this.overlay) {\n return;\n }\n const { signal } = this.abortController;\n this.overlay.addEventListener(\n 'pointerenter',\n () => this.handleHostPointerenter(),\n { signal }\n );\n this.overlay.addEventListener(\n 'pointerleave',\n () => this.handleHostPointerleave(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusin',\n () => this.handleOverlayFocusin(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusout',\n () => this.handleOverlayFocusout(),\n { signal }\n );\n this.overlay.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "aAYA,OAAS,4BAAAA,MAAgC,mEACzC,OAAS,YAAAC,MAAgB,mDAEzB,OAAS,QAAAC,MAAY,uBACrB,OACE,yBAAAC,EACA,oBAAAC,EACA,uBAAAC,MACK,
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.js';\nimport {\n InteractionController,\n InteractionTypes,\n lastInteractionType,\n} from './InteractionController.js';\nimport type { Overlay } from './Overlay.js';\n\nexport class HoverController extends InteractionController {\n override type = InteractionTypes.hover;\n\n private elementIds: string[] = [];\n\n private targetFocused = false;\n\n private hoverTimeout?: ReturnType<typeof setTimeout>;\n\n private hovering = false;\n\n private overlayFocused = false;\n\n handleKeyup(event: KeyboardEvent): void {\n if (event.code === 'Tab') {\n this.open = true;\n } else if (event.code === 'Escape') {\n if (this.open) {\n event.preventDefault();\n event.stopPropagation();\n this.open = false;\n // Return focus to trigger element\n if (this.target) {\n this.target.focus();\n }\n }\n }\n }\n\n handleTargetFocusin(): void {\n if (!this.target.matches(':focus-visible')) {\n return;\n }\n\n // Don't open hover overlay if the last interaction was a click.\n // This prevents hover from opening when focus returns to trigger\n // after closing a modal overlay via Escape key.\n // Previously this check was WebKit-only, but Chromium also needs it\n // because it reports :focus-visible=true for programmatic focus after keyboard events.\n if (this.target[lastInteractionType] === InteractionTypes.click) {\n return;\n }\n\n this.open = true;\n this.targetFocused = true;\n }\n\n handleTargetFocusout(): void {\n this.targetFocused = false;\n // Don't close immediately if pointer is over the content\n if (this.hovering) {\n return;\n }\n // Use delay to allow focus to move into overlay content\n this.doFocusleave();\n }\n\n private clearCloseTimeout(): void {\n if (this.hoverTimeout) {\n clearTimeout(this.hoverTimeout);\n this.hoverTimeout = undefined;\n }\n }\n\n handleTargetPointerenter(): void {\n this.clearCloseTimeout();\n if (this.overlay?.disabled) {\n return;\n }\n this.open = true;\n this.hovering = true;\n }\n\n handleTargetPointerleave(): void {\n this.doPointerleave();\n }\n\n // set a timeout once the pointer enters and the overlay is shown\n // give the user time to enter the overlay\n handleHostPointerenter(): void {\n this.clearCloseTimeout();\n }\n\n handleHostPointerleave(): void {\n this.doPointerleave();\n }\n\n handleOverlayFocusin(): void {\n this.overlayFocused = true;\n // Clear any pending close timeout when focus enters overlay\n this.clearCloseTimeout();\n }\n\n handleOverlayFocusout(): void {\n this.overlayFocused = false;\n // Don't close immediately if pointer is over the content or trigger has focus\n if (this.hovering) {\n return;\n }\n if (this.targetFocused && this.target.matches(':focus-visible')) {\n return;\n }\n // Use delay before closing\n this.doFocusleave();\n }\n\n override prepareDescription(): void {\n // require \"content\" to apply relationship\n if (!this.overlay || !this.overlay.elements.length) {\n return;\n }\n // When describeTrigger is 'none', do not set aria-describedby on the trigger (visual hint only).\n const overlay = this.overlay as Overlay;\n if (overlay.describeTrigger === 'none') {\n return;\n }\n\n const triggerRoot = this.target.getRootNode();\n const contentRoot = this.overlay.elements[0].getRootNode();\n const overlayRoot = this.overlay.getRootNode();\n if (triggerRoot === overlayRoot) {\n this.prepareOverlayRelativeDescription();\n } else if (triggerRoot === contentRoot) {\n this.prepareContentRelativeDescription();\n }\n }\n\n private prepareOverlayRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n [this.overlay.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n this.releaseDescription = noop;\n };\n }\n\n private prepareContentRelativeDescription(): void {\n if (!this.overlay) {\n return;\n }\n const overlay = this.overlay; // Capture for closure\n const elementIds: string[] = [];\n const appliedIds = overlay.elements.map((el) => {\n elementIds.push(el.id);\n if (!el.id) {\n el.id = `${overlay.tagName.toLowerCase()}-helper-${randomID()}`;\n }\n return el.id;\n });\n this.elementIds = elementIds;\n const releaseDescription = conditionAttributeWithId(\n this.target,\n 'aria-describedby',\n appliedIds\n );\n this.releaseDescription = () => {\n releaseDescription();\n overlay.elements.map((el, index) => {\n el.id = this.elementIds[index];\n });\n this.releaseDescription = noop;\n };\n }\n\n private scheduleClose(): void {\n this.hoverTimeout = setTimeout(() => {\n this.open = false;\n }, 300);\n }\n\n private doPointerleave(): void {\n this.hovering = false;\n const triggerElement = this.target as HTMLElement;\n if (this.targetFocused && triggerElement.matches(':focus-visible')) {\n return;\n }\n // Don't close if focus is within overlay content\n if (this.overlayFocused) {\n return;\n }\n\n this.scheduleClose();\n }\n\n private doFocusleave(): void {\n // Clear any existing timeout\n this.clearCloseTimeout();\n\n // Use same delay as pointer interactions for consistency\n if (!this.targetFocused && !this.overlayFocused && !this.hovering) {\n this.scheduleClose();\n }\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n this.target.addEventListener('focusin', () => this.handleTargetFocusin(), {\n signal,\n });\n this.target.addEventListener(\n 'focusout',\n () => this.handleTargetFocusout(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerenter',\n () => this.handleTargetPointerenter(),\n { signal }\n );\n this.target.addEventListener(\n 'pointerleave',\n () => this.handleTargetPointerleave(),\n { signal }\n );\n if (this.overlay) {\n this.initOverlay();\n }\n }\n\n override initOverlay(): void {\n if (!this.abortController || !this.overlay) {\n return;\n }\n const { signal } = this.abortController;\n this.overlay.addEventListener(\n 'pointerenter',\n () => this.handleHostPointerenter(),\n { signal }\n );\n this.overlay.addEventListener(\n 'pointerleave',\n () => this.handleHostPointerleave(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusin',\n () => this.handleOverlayFocusin(),\n { signal }\n );\n this.overlay.addEventListener(\n 'focusout',\n () => this.handleOverlayFocusout(),\n { signal }\n );\n this.overlay.addEventListener('keyup', (event) => this.handleKeyup(event), {\n signal,\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["conditionAttributeWithId", "randomID", "noop", "InteractionController", "InteractionTypes", "lastInteractionType", "event", "_a", "triggerRoot", "contentRoot", "overlayRoot", "releaseDescription", "overlay", "elementIds", "appliedIds", "el", "index", "triggerElement", "signal"]
|
|
7
7
|
}
|
|
@@ -104,6 +104,10 @@ export class LongpressController extends InteractionController {
|
|
|
104
104
|
) {
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
+
const overlay = this.overlay;
|
|
108
|
+
if (overlay.describeTrigger === "none") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
107
111
|
const longpressDescription = document.createElement("div");
|
|
108
112
|
longpressDescription.id = `longpress-describedby-descriptor-${randomID()}`;
|
|
109
113
|
const messageType = isIOS() || isAndroid() ? "touch" : "keyboard";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["LongpressController.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport {\n isAndroid,\n isIOS,\n} from '@spectrum-web-components/shared/src/platform.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.dev.js'\nimport {\n InteractionController,\n InteractionTypes,\n} from './InteractionController.dev.js'\n\nconst LONGPRESS_DURATION = 300;\nexport const LONGPRESS_INSTRUCTIONS = {\n touch: 'Double tap and long press for additional options',\n keyboard: 'Press Space or Alt+Down Arrow for additional options',\n mouse: 'Click and hold for additional options',\n};\n\ntype LongpressEvent = {\n source: 'pointer' | 'keyboard';\n};\n\nexport class LongpressController extends InteractionController {\n override type = InteractionTypes.longpress;\n\n override get activelyOpening(): boolean {\n return (\n this.longpressState === 'opening' || this.longpressState === 'pressed'\n );\n }\n\n protected longpressState: null | 'potential' | 'opening' | 'pressed' = null;\n\n override releaseDescription = noop;\n\n private timeout!: ReturnType<typeof setTimeout>;\n\n handleLongpress(): void {\n this.open = true;\n this.longpressState =\n this.longpressState === 'potential' ? 'opening' : 'pressed';\n }\n\n handlePointerdown(event: PointerEvent): void {\n if (!this.target) {\n return;\n }\n if (event.button !== 0) {\n return;\n }\n this.longpressState = 'potential';\n document.addEventListener('pointerup', this.handlePointerup);\n document.addEventListener('pointercancel', this.handlePointerup);\n // Only dispatch longpress event if the trigger element isn't doing it for us.\n const triggerHandlesLongpress = 'holdAffordance' in this.target;\n if (triggerHandlesLongpress) {\n return;\n }\n this.timeout = setTimeout(() => {\n if (!this.target) {\n return;\n }\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'pointer',\n },\n })\n );\n }, LONGPRESS_DURATION);\n }\n\n private handlePointerup = (): void => {\n clearTimeout(this.timeout);\n if (!this.target) {\n return;\n }\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState = this.overlay?.state === 'opening' ? 'pressed' : null;\n document.removeEventListener('pointerup', this.handlePointerup);\n document.removeEventListener('pointercancel', this.handlePointerup);\n };\n\n private handleKeydown(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (altKey && code === 'ArrowDown') {\n event.stopImmediatePropagation();\n }\n }\n\n private handleKeyup(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (code === 'Space' || (altKey && code === 'ArrowDown')) {\n if (!this.target) {\n return;\n }\n event.stopPropagation();\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'keyboard',\n },\n })\n );\n setTimeout(() => {\n this.longpressState = null;\n });\n }\n }\n\n override prepareDescription(trigger: HTMLElement): void {\n if (\n // do not reapply until target is recycled\n this.releaseDescription !== noop ||\n // require \"longpress content\" to apply relationship\n !this.overlay ||\n !this.overlay.elements.length\n ) {\n return;\n }\n\n const longpressDescription = document.createElement('div');\n longpressDescription.id = `longpress-describedby-descriptor-${randomID()}`;\n const messageType = isIOS() || isAndroid() ? 'touch' : 'keyboard';\n longpressDescription.textContent = LONGPRESS_INSTRUCTIONS[messageType];\n longpressDescription.slot = 'longpress-describedby-descriptor';\n const triggerParent = trigger.getRootNode() as HTMLElement;\n const overlayParent = this.overlay.getRootNode() as HTMLElement;\n // Manage the placement of the helper element in an accessible place with\n // the lowest chance of negatively affecting the layout of the page.\n if (triggerParent === overlayParent) {\n // Trigger and Overlay in same DOM tree...\n // Append helper element to Overlay.\n this.overlay.append(longpressDescription);\n } else {\n // If Trigger in <body>, hide helper\n longpressDescription.hidden = !('host' in triggerParent);\n // Trigger and Overlay in different DOM tree, Trigger in shadow tree...\n // Insert helper element after Trigger.\n trigger.insertAdjacentElement('afterend', longpressDescription);\n }\n\n const releaseDescription = conditionAttributeWithId(\n trigger,\n 'aria-describedby',\n [longpressDescription.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n longpressDescription.remove();\n this.releaseDescription = noop;\n };\n }\n\n override shouldCompleteOpen(): void {\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState =\n this.longpressState === 'pressed' ? null : this.longpressState;\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('longpress', () => this.handleLongpress(), {\n signal,\n });\n this.target.addEventListener(\n 'pointerdown',\n (event: PointerEvent) => this.handlePointerdown(event),\n { signal }\n );\n\n this.prepareDescription(this.target);\n if (\n (this.target as HTMLElement & { holdAffordance: boolean }).holdAffordance\n ) {\n // Only bind keyboard events when the trigger element isn't doing it for us.\n return;\n }\n this.target.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => this.handleKeydown(event),\n { signal }\n );\n this.target.addEventListener(\n 'keyup',\n (event: KeyboardEvent) => this.handleKeyup(event),\n { signal }\n );\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAYA,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AAEzB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport {\n isAndroid,\n isIOS,\n} from '@spectrum-web-components/shared/src/platform.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.dev.js'\nimport {\n InteractionController,\n InteractionTypes,\n} from './InteractionController.dev.js'\nimport type { Overlay } from './Overlay.dev.js'\n\nconst LONGPRESS_DURATION = 300;\nexport const LONGPRESS_INSTRUCTIONS = {\n touch: 'Double tap and long press for additional options',\n keyboard: 'Press Space or Alt+Down Arrow for additional options',\n mouse: 'Click and hold for additional options',\n};\n\ntype LongpressEvent = {\n source: 'pointer' | 'keyboard';\n};\n\nexport class LongpressController extends InteractionController {\n override type = InteractionTypes.longpress;\n\n override get activelyOpening(): boolean {\n return (\n this.longpressState === 'opening' || this.longpressState === 'pressed'\n );\n }\n\n protected longpressState: null | 'potential' | 'opening' | 'pressed' = null;\n\n override releaseDescription = noop;\n\n private timeout!: ReturnType<typeof setTimeout>;\n\n handleLongpress(): void {\n this.open = true;\n this.longpressState =\n this.longpressState === 'potential' ? 'opening' : 'pressed';\n }\n\n handlePointerdown(event: PointerEvent): void {\n if (!this.target) {\n return;\n }\n if (event.button !== 0) {\n return;\n }\n this.longpressState = 'potential';\n document.addEventListener('pointerup', this.handlePointerup);\n document.addEventListener('pointercancel', this.handlePointerup);\n // Only dispatch longpress event if the trigger element isn't doing it for us.\n const triggerHandlesLongpress = 'holdAffordance' in this.target;\n if (triggerHandlesLongpress) {\n return;\n }\n this.timeout = setTimeout(() => {\n if (!this.target) {\n return;\n }\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'pointer',\n },\n })\n );\n }, LONGPRESS_DURATION);\n }\n\n private handlePointerup = (): void => {\n clearTimeout(this.timeout);\n if (!this.target) {\n return;\n }\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState = this.overlay?.state === 'opening' ? 'pressed' : null;\n document.removeEventListener('pointerup', this.handlePointerup);\n document.removeEventListener('pointercancel', this.handlePointerup);\n };\n\n private handleKeydown(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (altKey && code === 'ArrowDown') {\n event.stopImmediatePropagation();\n }\n }\n\n private handleKeyup(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (code === 'Space' || (altKey && code === 'ArrowDown')) {\n if (!this.target) {\n return;\n }\n event.stopPropagation();\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'keyboard',\n },\n })\n );\n setTimeout(() => {\n this.longpressState = null;\n });\n }\n }\n\n override prepareDescription(trigger: HTMLElement): void {\n if (\n // do not reapply until target is recycled\n this.releaseDescription !== noop ||\n // require \"longpress content\" to apply relationship\n !this.overlay ||\n !this.overlay.elements.length\n ) {\n return;\n }\n // When describeTrigger is 'none', do not set aria-describedby on the trigger (visual hint only).\n const overlay = this.overlay as Overlay;\n if (overlay.describeTrigger === 'none') {\n return;\n }\n\n const longpressDescription = document.createElement('div');\n longpressDescription.id = `longpress-describedby-descriptor-${randomID()}`;\n const messageType = isIOS() || isAndroid() ? 'touch' : 'keyboard';\n longpressDescription.textContent = LONGPRESS_INSTRUCTIONS[messageType];\n longpressDescription.slot = 'longpress-describedby-descriptor';\n const triggerParent = trigger.getRootNode() as HTMLElement;\n const overlayParent = this.overlay.getRootNode() as HTMLElement;\n // Manage the placement of the helper element in an accessible place with\n // the lowest chance of negatively affecting the layout of the page.\n if (triggerParent === overlayParent) {\n // Trigger and Overlay in same DOM tree...\n // Append helper element to Overlay.\n this.overlay.append(longpressDescription);\n } else {\n // If Trigger in <body>, hide helper\n longpressDescription.hidden = !('host' in triggerParent);\n // Trigger and Overlay in different DOM tree, Trigger in shadow tree...\n // Insert helper element after Trigger.\n trigger.insertAdjacentElement('afterend', longpressDescription);\n }\n\n const releaseDescription = conditionAttributeWithId(\n trigger,\n 'aria-describedby',\n [longpressDescription.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n longpressDescription.remove();\n this.releaseDescription = noop;\n };\n }\n\n override shouldCompleteOpen(): void {\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState =\n this.longpressState === 'pressed' ? null : this.longpressState;\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('longpress', () => this.handleLongpress(), {\n signal,\n });\n this.target.addEventListener(\n 'pointerdown',\n (event: PointerEvent) => this.handlePointerdown(event),\n { signal }\n );\n\n this.prepareDescription(this.target);\n if (\n (this.target as HTMLElement & { holdAffordance: boolean }).holdAffordance\n ) {\n // Only bind keyboard events when the trigger element isn't doing it for us.\n return;\n }\n this.target.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => this.handleKeydown(event),\n { signal }\n );\n this.target.addEventListener(\n 'keyup',\n (event: KeyboardEvent) => this.handleKeyup(event),\n { signal }\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";import{conditionAttributeWithId as
|
|
1
|
+
"use strict";import{conditionAttributeWithId as d}from"@spectrum-web-components/base/src/condition-attribute-with-id.js";import{isAndroid as l,isIOS as p}from"@spectrum-web-components/shared/src/platform.js";import{randomID as h}from"@spectrum-web-components/shared/src/random-id.js";import{noop as r}from"./AbstractOverlay.js";import{InteractionController as c,InteractionTypes as g}from"./InteractionController.js";const u=300;export const LONGPRESS_INSTRUCTIONS={touch:"Double tap and long press for additional options",keyboard:"Press Space or Alt+Down Arrow for additional options",mouse:"Click and hold for additional options"};export class LongpressController extends c{constructor(){super(...arguments);this.type=g.longpress;this.longpressState=null;this.releaseDescription=r;this.handlePointerup=()=>{var e;clearTimeout(this.timeout),this.target&&(this.longpressState=((e=this.overlay)==null?void 0:e.state)==="opening"?"pressed":null,document.removeEventListener("pointerup",this.handlePointerup),document.removeEventListener("pointercancel",this.handlePointerup))}}get activelyOpening(){return this.longpressState==="opening"||this.longpressState==="pressed"}handleLongpress(){this.open=!0,this.longpressState=this.longpressState==="potential"?"opening":"pressed"}handlePointerdown(e){!this.target||e.button!==0||(this.longpressState="potential",document.addEventListener("pointerup",this.handlePointerup),document.addEventListener("pointercancel",this.handlePointerup),"holdAffordance"in this.target)||(this.timeout=setTimeout(()=>{this.target&&this.target.dispatchEvent(new CustomEvent("longpress",{bubbles:!0,composed:!0,detail:{source:"pointer"}}))},u))}handleKeydown(e){const{code:o,altKey:t}=e;t&&o==="ArrowDown"&&e.stopImmediatePropagation()}handleKeyup(e){const{code:o,altKey:t}=e;if(o==="Space"||t&&o==="ArrowDown"){if(!this.target)return;e.stopPropagation(),this.target.dispatchEvent(new CustomEvent("longpress",{bubbles:!0,composed:!0,detail:{source:"keyboard"}})),setTimeout(()=>{this.longpressState=null})}}prepareDescription(e){if(this.releaseDescription!==r||!this.overlay||!this.overlay.elements.length||this.overlay.describeTrigger==="none")return;const t=document.createElement("div");t.id=`longpress-describedby-descriptor-${h()}`;const s=p()||l()?"touch":"keyboard";t.textContent=LONGPRESS_INSTRUCTIONS[s],t.slot="longpress-describedby-descriptor";const n=e.getRootNode(),i=this.overlay.getRootNode();n===i?this.overlay.append(t):(t.hidden=!("host"in n),e.insertAdjacentElement("afterend",t));const a=d(e,"aria-describedby",[t.id]);this.releaseDescription=()=>{a(),t.remove(),this.releaseDescription=r}}shouldCompleteOpen(){this.longpressState=this.longpressState==="pressed"?null:this.longpressState}init(){var o;(o=this.abortController)==null||o.abort(),this.abortController=new AbortController;const{signal:e}=this.abortController;this.target.addEventListener("longpress",()=>this.handleLongpress(),{signal:e}),this.target.addEventListener("pointerdown",t=>this.handlePointerdown(t),{signal:e}),this.prepareDescription(this.target),!this.target.holdAffordance&&(this.target.addEventListener("keydown",t=>this.handleKeydown(t),{signal:e}),this.target.addEventListener("keyup",t=>this.handleKeyup(t),{signal:e}))}}
|
|
2
2
|
//# sourceMappingURL=LongpressController.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["LongpressController.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport {\n isAndroid,\n isIOS,\n} from '@spectrum-web-components/shared/src/platform.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.js';\nimport {\n InteractionController,\n InteractionTypes,\n} from './InteractionController.js';\n\nconst LONGPRESS_DURATION = 300;\nexport const LONGPRESS_INSTRUCTIONS = {\n touch: 'Double tap and long press for additional options',\n keyboard: 'Press Space or Alt+Down Arrow for additional options',\n mouse: 'Click and hold for additional options',\n};\n\ntype LongpressEvent = {\n source: 'pointer' | 'keyboard';\n};\n\nexport class LongpressController extends InteractionController {\n override type = InteractionTypes.longpress;\n\n override get activelyOpening(): boolean {\n return (\n this.longpressState === 'opening' || this.longpressState === 'pressed'\n );\n }\n\n protected longpressState: null | 'potential' | 'opening' | 'pressed' = null;\n\n override releaseDescription = noop;\n\n private timeout!: ReturnType<typeof setTimeout>;\n\n handleLongpress(): void {\n this.open = true;\n this.longpressState =\n this.longpressState === 'potential' ? 'opening' : 'pressed';\n }\n\n handlePointerdown(event: PointerEvent): void {\n if (!this.target) {\n return;\n }\n if (event.button !== 0) {\n return;\n }\n this.longpressState = 'potential';\n document.addEventListener('pointerup', this.handlePointerup);\n document.addEventListener('pointercancel', this.handlePointerup);\n // Only dispatch longpress event if the trigger element isn't doing it for us.\n const triggerHandlesLongpress = 'holdAffordance' in this.target;\n if (triggerHandlesLongpress) {\n return;\n }\n this.timeout = setTimeout(() => {\n if (!this.target) {\n return;\n }\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'pointer',\n },\n })\n );\n }, LONGPRESS_DURATION);\n }\n\n private handlePointerup = (): void => {\n clearTimeout(this.timeout);\n if (!this.target) {\n return;\n }\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState = this.overlay?.state === 'opening' ? 'pressed' : null;\n document.removeEventListener('pointerup', this.handlePointerup);\n document.removeEventListener('pointercancel', this.handlePointerup);\n };\n\n private handleKeydown(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (altKey && code === 'ArrowDown') {\n event.stopImmediatePropagation();\n }\n }\n\n private handleKeyup(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (code === 'Space' || (altKey && code === 'ArrowDown')) {\n if (!this.target) {\n return;\n }\n event.stopPropagation();\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'keyboard',\n },\n })\n );\n setTimeout(() => {\n this.longpressState = null;\n });\n }\n }\n\n override prepareDescription(trigger: HTMLElement): void {\n if (\n // do not reapply until target is recycled\n this.releaseDescription !== noop ||\n // require \"longpress content\" to apply relationship\n !this.overlay ||\n !this.overlay.elements.length\n ) {\n return;\n }\n\n const longpressDescription = document.createElement('div');\n longpressDescription.id = `longpress-describedby-descriptor-${randomID()}`;\n const messageType = isIOS() || isAndroid() ? 'touch' : 'keyboard';\n longpressDescription.textContent = LONGPRESS_INSTRUCTIONS[messageType];\n longpressDescription.slot = 'longpress-describedby-descriptor';\n const triggerParent = trigger.getRootNode() as HTMLElement;\n const overlayParent = this.overlay.getRootNode() as HTMLElement;\n // Manage the placement of the helper element in an accessible place with\n // the lowest chance of negatively affecting the layout of the page.\n if (triggerParent === overlayParent) {\n // Trigger and Overlay in same DOM tree...\n // Append helper element to Overlay.\n this.overlay.append(longpressDescription);\n } else {\n // If Trigger in <body>, hide helper\n longpressDescription.hidden = !('host' in triggerParent);\n // Trigger and Overlay in different DOM tree, Trigger in shadow tree...\n // Insert helper element after Trigger.\n trigger.insertAdjacentElement('afterend', longpressDescription);\n }\n\n const releaseDescription = conditionAttributeWithId(\n trigger,\n 'aria-describedby',\n [longpressDescription.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n longpressDescription.remove();\n this.releaseDescription = noop;\n };\n }\n\n override shouldCompleteOpen(): void {\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState =\n this.longpressState === 'pressed' ? null : this.longpressState;\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('longpress', () => this.handleLongpress(), {\n signal,\n });\n this.target.addEventListener(\n 'pointerdown',\n (event: PointerEvent) => this.handlePointerdown(event),\n { signal }\n );\n\n this.prepareDescription(this.target);\n if (\n (this.target as HTMLElement & { holdAffordance: boolean }).holdAffordance\n ) {\n // Only bind keyboard events when the trigger element isn't doing it for us.\n return;\n }\n this.target.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => this.handleKeydown(event),\n { signal }\n );\n this.target.addEventListener(\n 'keyup',\n (event: KeyboardEvent) => this.handleKeyup(event),\n { signal }\n );\n }\n}\n"],
|
|
5
|
-
"mappings": "aAYA,OAAS,4BAAAA,MAAgC,mEACzC,OACE,aAAAC,EACA,SAAAC,MACK,kDACP,OAAS,YAAAC,MAAgB,mDAEzB,OAAS,QAAAC,MAAY,uBACrB,OACE,yBAAAC,EACA,oBAAAC,MACK,
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { conditionAttributeWithId } from '@spectrum-web-components/base/src/condition-attribute-with-id.js';\nimport {\n isAndroid,\n isIOS,\n} from '@spectrum-web-components/shared/src/platform.js';\nimport { randomID } from '@spectrum-web-components/shared/src/random-id.js';\n\nimport { noop } from './AbstractOverlay.js';\nimport {\n InteractionController,\n InteractionTypes,\n} from './InteractionController.js';\nimport type { Overlay } from './Overlay.js';\n\nconst LONGPRESS_DURATION = 300;\nexport const LONGPRESS_INSTRUCTIONS = {\n touch: 'Double tap and long press for additional options',\n keyboard: 'Press Space or Alt+Down Arrow for additional options',\n mouse: 'Click and hold for additional options',\n};\n\ntype LongpressEvent = {\n source: 'pointer' | 'keyboard';\n};\n\nexport class LongpressController extends InteractionController {\n override type = InteractionTypes.longpress;\n\n override get activelyOpening(): boolean {\n return (\n this.longpressState === 'opening' || this.longpressState === 'pressed'\n );\n }\n\n protected longpressState: null | 'potential' | 'opening' | 'pressed' = null;\n\n override releaseDescription = noop;\n\n private timeout!: ReturnType<typeof setTimeout>;\n\n handleLongpress(): void {\n this.open = true;\n this.longpressState =\n this.longpressState === 'potential' ? 'opening' : 'pressed';\n }\n\n handlePointerdown(event: PointerEvent): void {\n if (!this.target) {\n return;\n }\n if (event.button !== 0) {\n return;\n }\n this.longpressState = 'potential';\n document.addEventListener('pointerup', this.handlePointerup);\n document.addEventListener('pointercancel', this.handlePointerup);\n // Only dispatch longpress event if the trigger element isn't doing it for us.\n const triggerHandlesLongpress = 'holdAffordance' in this.target;\n if (triggerHandlesLongpress) {\n return;\n }\n this.timeout = setTimeout(() => {\n if (!this.target) {\n return;\n }\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'pointer',\n },\n })\n );\n }, LONGPRESS_DURATION);\n }\n\n private handlePointerup = (): void => {\n clearTimeout(this.timeout);\n if (!this.target) {\n return;\n }\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState = this.overlay?.state === 'opening' ? 'pressed' : null;\n document.removeEventListener('pointerup', this.handlePointerup);\n document.removeEventListener('pointercancel', this.handlePointerup);\n };\n\n private handleKeydown(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (altKey && code === 'ArrowDown') {\n event.stopImmediatePropagation();\n }\n }\n\n private handleKeyup(event: KeyboardEvent): void {\n const { code, altKey } = event;\n if (code === 'Space' || (altKey && code === 'ArrowDown')) {\n if (!this.target) {\n return;\n }\n event.stopPropagation();\n this.target.dispatchEvent(\n new CustomEvent<LongpressEvent>('longpress', {\n bubbles: true,\n composed: true,\n detail: {\n source: 'keyboard',\n },\n })\n );\n setTimeout(() => {\n this.longpressState = null;\n });\n }\n }\n\n override prepareDescription(trigger: HTMLElement): void {\n if (\n // do not reapply until target is recycled\n this.releaseDescription !== noop ||\n // require \"longpress content\" to apply relationship\n !this.overlay ||\n !this.overlay.elements.length\n ) {\n return;\n }\n // When describeTrigger is 'none', do not set aria-describedby on the trigger (visual hint only).\n const overlay = this.overlay as Overlay;\n if (overlay.describeTrigger === 'none') {\n return;\n }\n\n const longpressDescription = document.createElement('div');\n longpressDescription.id = `longpress-describedby-descriptor-${randomID()}`;\n const messageType = isIOS() || isAndroid() ? 'touch' : 'keyboard';\n longpressDescription.textContent = LONGPRESS_INSTRUCTIONS[messageType];\n longpressDescription.slot = 'longpress-describedby-descriptor';\n const triggerParent = trigger.getRootNode() as HTMLElement;\n const overlayParent = this.overlay.getRootNode() as HTMLElement;\n // Manage the placement of the helper element in an accessible place with\n // the lowest chance of negatively affecting the layout of the page.\n if (triggerParent === overlayParent) {\n // Trigger and Overlay in same DOM tree...\n // Append helper element to Overlay.\n this.overlay.append(longpressDescription);\n } else {\n // If Trigger in <body>, hide helper\n longpressDescription.hidden = !('host' in triggerParent);\n // Trigger and Overlay in different DOM tree, Trigger in shadow tree...\n // Insert helper element after Trigger.\n trigger.insertAdjacentElement('afterend', longpressDescription);\n }\n\n const releaseDescription = conditionAttributeWithId(\n trigger,\n 'aria-describedby',\n [longpressDescription.id]\n );\n this.releaseDescription = () => {\n releaseDescription();\n longpressDescription.remove();\n this.releaseDescription = noop;\n };\n }\n\n override shouldCompleteOpen(): void {\n // When triggered by the pointer, the last of `opened`\n // or `pointerup` should move the `longpressState` to\n // `null` so that the earlier event can void the \"light\n // dismiss\" and keep the Overlay open.\n this.longpressState =\n this.longpressState === 'pressed' ? null : this.longpressState;\n }\n\n override init(): void {\n // Clean up listeners if they've already been bound\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n this.target.addEventListener('longpress', () => this.handleLongpress(), {\n signal,\n });\n this.target.addEventListener(\n 'pointerdown',\n (event: PointerEvent) => this.handlePointerdown(event),\n { signal }\n );\n\n this.prepareDescription(this.target);\n if (\n (this.target as HTMLElement & { holdAffordance: boolean }).holdAffordance\n ) {\n // Only bind keyboard events when the trigger element isn't doing it for us.\n return;\n }\n this.target.addEventListener(\n 'keydown',\n (event: KeyboardEvent) => this.handleKeydown(event),\n { signal }\n );\n this.target.addEventListener(\n 'keyup',\n (event: KeyboardEvent) => this.handleKeyup(event),\n { signal }\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["conditionAttributeWithId", "isAndroid", "isIOS", "randomID", "noop", "InteractionController", "InteractionTypes", "LONGPRESS_DURATION", "_a", "event", "code", "altKey", "trigger", "longpressDescription", "messageType", "triggerParent", "overlayParent", "releaseDescription", "signal"]
|
|
7
7
|
}
|
package/src/Overlay.d.ts
CHANGED
|
@@ -223,6 +223,17 @@ export declare class Overlay extends ComputedOverlayBase {
|
|
|
223
223
|
* @type {TriggerInteraction}
|
|
224
224
|
*/
|
|
225
225
|
triggerInteraction?: TriggerInteraction;
|
|
226
|
+
/**
|
|
227
|
+
* When set to `'none'`, the overlay will not set `aria-describedby` on the
|
|
228
|
+
* trigger when open, so the trigger is not described by the overlay content.
|
|
229
|
+
* Use for hint overlays whose content duplicates the trigger (e.g. truncated
|
|
230
|
+
* value tooltips) to avoid double announcement by screen readers.
|
|
231
|
+
*
|
|
232
|
+
* @internal
|
|
233
|
+
* @type {"auto" | "none"}
|
|
234
|
+
* @default "auto"
|
|
235
|
+
*/
|
|
236
|
+
describeTrigger: 'auto' | 'none';
|
|
226
237
|
/**
|
|
227
238
|
* Configures the open/close heuristics of the Overlay.
|
|
228
239
|
*
|
package/src/Overlay.dev.js
CHANGED
|
@@ -66,6 +66,7 @@ const _Overlay = class _Overlay extends ComputedOverlayBase {
|
|
|
66
66
|
this.allowOutsideClick = false;
|
|
67
67
|
this._state = "closed";
|
|
68
68
|
this.triggerElement = null;
|
|
69
|
+
this.describeTrigger = "auto";
|
|
69
70
|
this.type = "auto";
|
|
70
71
|
/**
|
|
71
72
|
* Tracks whether the overlay was previously open.
|
|
@@ -112,8 +113,8 @@ const _Overlay = class _Overlay extends ComputedOverlayBase {
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
get delayed() {
|
|
115
|
-
|
|
116
|
-
return (
|
|
116
|
+
const lastElement = this.elements[this.elements.length - 1];
|
|
117
|
+
return (lastElement == null ? void 0 : lastElement.hasAttribute("delayed")) || this._delayed;
|
|
117
118
|
}
|
|
118
119
|
set delayed(delayed) {
|
|
119
120
|
this._delayed = delayed;
|
|
@@ -315,8 +316,9 @@ const _Overlay = class _Overlay extends ComputedOverlayBase {
|
|
|
315
316
|
}
|
|
316
317
|
if (targetOpenState) {
|
|
317
318
|
const focusTrap = await import("focus-trap");
|
|
319
|
+
const initialFocus = this.receivesFocus === "false" ? false : focusEl || void 0;
|
|
318
320
|
this._focusTrap = focusTrap.createFocusTrap(this.dialogEl, {
|
|
319
|
-
initialFocus
|
|
321
|
+
initialFocus,
|
|
320
322
|
tabbableOptions: {
|
|
321
323
|
getShadowRoot: true
|
|
322
324
|
},
|
|
@@ -837,6 +839,9 @@ __decorateClass([
|
|
|
837
839
|
__decorateClass([
|
|
838
840
|
property({ attribute: false })
|
|
839
841
|
], _Overlay.prototype, "triggerInteraction", 2);
|
|
842
|
+
__decorateClass([
|
|
843
|
+
property({ attribute: false })
|
|
844
|
+
], _Overlay.prototype, "describeTrigger", 2);
|
|
840
845
|
__decorateClass([
|
|
841
846
|
property()
|
|
842
847
|
], _Overlay.prototype, "type", 2);
|