From 19ac2389eb0843173f51a12de41ac808cd8f0569 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 21 Feb 2022 11:50:57 -0500 Subject: [PATCH] fix(img): draggable attribute is now inherited to inner img element (#24781) Resolves #21325 Co-authored-by: Celilsemi Sam Erkiner Co-authored-by: Liam DeBeasi --- .../components/back-button/back-button.tsx | 4 +- core/src/components/breadcrumb/breadcrumb.tsx | 4 +- core/src/components/button/button.tsx | 4 +- core/src/components/header/header.tsx | 4 +- core/src/components/img/img.tsx | 35 +++++++++-- core/src/components/img/test/draggable/e2e.ts | 17 ++++++ .../components/img/test/draggable/index.html | 60 +++++++++++++++++++ core/src/components/input/input.tsx | 4 +- .../components/menu-button/menu-button.tsx | 4 +- core/src/components/menu/menu.tsx | 4 +- core/src/components/range/range.tsx | 4 +- core/src/components/textarea/textarea.tsx | 4 +- core/src/utils/helpers.ts | 12 ++-- 13 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 core/src/components/img/test/draggable/e2e.ts create mode 100644 core/src/components/img/test/draggable/index.html diff --git a/core/src/components/back-button/back-button.tsx b/core/src/components/back-button/back-button.tsx index d0dfa521417..67681f3790e 100644 --- a/core/src/components/back-button/back-button.tsx +++ b/core/src/components/back-button/back-button.tsx @@ -5,7 +5,7 @@ import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; import { AnimationBuilder, Color } from '../../interface'; import { ButtonInterface } from '../../utils/element-interface'; -import { inheritAttributes } from '../../utils/helpers'; +import { Attributes, inheritAttributes } from '../../utils/helpers'; import { createColorClasses, hostContext, openURL } from '../../utils/theme'; /** @@ -24,7 +24,7 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme'; shadow: true }) export class BackButton implements ComponentInterface, ButtonInterface { - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLElement; diff --git a/core/src/components/breadcrumb/breadcrumb.tsx b/core/src/components/breadcrumb/breadcrumb.tsx index 0e512e9b438..990470e14ec 100644 --- a/core/src/components/breadcrumb/breadcrumb.tsx +++ b/core/src/components/breadcrumb/breadcrumb.tsx @@ -3,7 +3,7 @@ import { chevronForwardOutline, ellipsisHorizontal } from 'ionicons/icons'; import { getIonMode } from '../../global/ionic-global'; import { AnimationBuilder, BreadcrumbCollapsedClickEventDetail, Color, RouterDirection } from '../../interface'; -import { inheritAttributes } from '../../utils/helpers'; +import { Attributes, inheritAttributes } from '../../utils/helpers'; import { createColorClasses, hostContext, openURL } from '../../utils/theme'; /** @@ -22,7 +22,7 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme'; shadow: true }) export class Breadcrumb implements ComponentInterface { - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; private collapsedRef?: HTMLElement; /** @internal */ diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index f53b4da7d23..d6da758ad6a 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop import { getIonMode } from '../../global/ionic-global'; import { AnimationBuilder, Color, RouterDirection } from '../../interface'; import { AnchorInterface, ButtonInterface } from '../../utils/element-interface'; -import { hasShadowDom, inheritAttributes } from '../../utils/helpers'; +import { Attributes, hasShadowDom, inheritAttributes } from '../../utils/helpers'; import { createColorClasses, hostContext, openURL } from '../../utils/theme'; /** @@ -28,7 +28,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf private inItem = false; private inListHeader = false; private inToolbar = false; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLElement; diff --git a/core/src/components/header/header.tsx b/core/src/components/header/header.tsx index c03a18fb5a8..9f09803f9d7 100644 --- a/core/src/components/header/header.tsx +++ b/core/src/components/header/header.tsx @@ -1,7 +1,7 @@ import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core'; import { getIonMode } from '../../global/ionic-global'; -import { componentOnReady, inheritAttributes } from '../../utils/helpers'; +import { Attributes, componentOnReady, inheritAttributes } from '../../utils/helpers'; import { hostContext } from '../../utils/theme'; import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils'; @@ -21,7 +21,7 @@ export class Header implements ComponentInterface { private contentScrollCallback?: any; private intersectionObserver?: any; private collapsibleMainHeader?: HTMLElement; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLElement; diff --git a/core/src/components/img/img.tsx b/core/src/components/img/img.tsx index 0f8c81ea851..040d3fc49e6 100644 --- a/core/src/components/img/img.tsx +++ b/core/src/components/img/img.tsx @@ -1,6 +1,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core'; import { getIonMode } from '../../global/ionic-global'; +import { Attributes, inheritAttributes } from '../../utils/helpers'; /** * @part image - The inner `img` element. @@ -13,6 +14,7 @@ import { getIonMode } from '../../global/ionic-global'; export class Img implements ComponentInterface { private io?: IntersectionObserver; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLElement; @@ -45,6 +47,10 @@ export class Img implements ComponentInterface { /** Emitted when the img fails to load */ @Event() ionError!: EventEmitter; + componentWillLoad() { + this.inheritedAttributes = inheritAttributes(this.el, ['draggable']); + } + componentDidLoad() { this.addIO(); } @@ -100,17 +106,38 @@ export class Img implements ComponentInterface { } render() { + const { loadSrc, alt, onLoad, loadError, inheritedAttributes } = this; + const { draggable } = inheritedAttributes; return ( {this.alt} ); } } + +/** + * Enumerated strings must be set as booleans + * as Stencil will not render 'false' in the DOM. + * The need to explicitly render draggable="true" + * as only certain elements are draggable by default. + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable. + */ +const isDraggable = (draggable?: string): boolean | undefined => { + switch (draggable) { + case 'true': + return true; + case 'false': + return false; + default: + return undefined; + } +} diff --git a/core/src/components/img/test/draggable/e2e.ts b/core/src/components/img/test/draggable/e2e.ts new file mode 100644 index 00000000000..51836d97db3 --- /dev/null +++ b/core/src/components/img/test/draggable/e2e.ts @@ -0,0 +1,17 @@ +import { newE2EPage } from '@stencil/core/testing'; + +test('img: draggable', async () => { + const page = await newE2EPage({ + url: '/src/components/img/test/draggable?ionic:_testing=true' + }); + + const imgDraggableTrue = await page.find('#img-draggable-true >>> img'); + expect(imgDraggableTrue.getAttribute('draggable')).toEqual('true'); + + const imgDraggableFalse = await page.find('#img-draggable-false >>> img'); + expect(imgDraggableFalse.getAttribute('draggable')).toEqual('false'); + + const imgDraggableUnset = await page.find('#img-draggable-unset >>> img'); + expect(imgDraggableUnset.getAttribute('draggable')).toEqual(null); + +}); diff --git a/core/src/components/img/test/draggable/index.html b/core/src/components/img/test/draggable/index.html new file mode 100644 index 00000000000..fd64b5b2648 --- /dev/null +++ b/core/src/components/img/test/draggable/index.html @@ -0,0 +1,60 @@ + + + + + + Img - Draggable + + + + + + + + + + + + + + + + Img - Draggable + + + + + + + Draggable + + + + + Not draggable (draggable="false") + + + + + Draggable (draggable not set) + + + + + + + + + + diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index e874c1d31f6..0ba552bdf87 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -2,7 +2,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos import { getIonMode } from '../../global/ionic-global'; import { AutocompleteTypes, Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface'; -import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers'; +import { Attributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers'; import { createColorClasses } from '../../utils/theme'; /** @@ -21,7 +21,7 @@ export class Input implements ComponentInterface { private nativeInput?: HTMLInputElement; private inputId = `ion-input-${inputIds++}`; private didBlurAfterEdit = false; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; private isComposing = false; /** diff --git a/core/src/components/menu-button/menu-button.tsx b/core/src/components/menu-button/menu-button.tsx index 33944e44869..cadb71a28b5 100644 --- a/core/src/components/menu-button/menu-button.tsx +++ b/core/src/components/menu-button/menu-button.tsx @@ -5,7 +5,7 @@ import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; import { Color } from '../../interface'; import { ButtonInterface } from '../../utils/element-interface'; -import { inheritAttributes } from '../../utils/helpers'; +import { Attributes, inheritAttributes } from '../../utils/helpers'; import { menuController } from '../../utils/menu-controller'; import { createColorClasses, hostContext } from '../../utils/theme'; import { updateVisibility } from '../menu-toggle/menu-toggle-util'; @@ -25,7 +25,7 @@ import { updateVisibility } from '../menu-toggle/menu-toggle-util'; shadow: true }) export class MenuButton implements ComponentInterface, ButtonInterface { - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLIonSegmentElement; diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 56c642c235a..b44dfad9da6 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -5,7 +5,7 @@ import { getIonMode } from '../../global/ionic-global'; import { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, Side } from '../../interface'; import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier'; import { GESTURE_CONTROLLER } from '../../utils/gesture'; -import { assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers'; +import { Attributes, assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers'; import { menuController } from '../../utils/menu-controller'; import { getOverlay } from '../../utils/overlays'; @@ -43,7 +43,7 @@ export class Menu implements ComponentInterface, MenuI { contentEl?: HTMLElement; lastFocus?: HTMLElement; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; private handleFocus = (ev: FocusEvent) => { /** diff --git a/core/src/components/range/range.tsx b/core/src/components/range/range.tsx index 760a06cb434..93dea8a662d 100644 --- a/core/src/components/range/range.tsx +++ b/core/src/components/range/range.tsx @@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop import { getIonMode } from '../../global/ionic-global'; import { Color, Gesture, GestureDetail, KnobName, RangeChangeEventDetail, RangeValue, StyleEventDetail } from '../../interface'; -import { clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers'; +import { Attributes, clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers'; import { isRTL } from '../../utils/rtl'; import { createColorClasses, hostContext } from '../../utils/theme'; @@ -38,7 +38,7 @@ export class Range implements ComponentInterface { private hasFocus = false; private rangeSlider?: HTMLElement; private gesture?: Gesture; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; @Element() el!: HTMLIonRangeElement; diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index 68f733ee558..f234f94dae3 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -2,7 +2,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos import { getIonMode } from '../../global/ionic-global'; import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface'; -import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers'; +import { Attributes, debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers'; import { createColorClasses } from '../../utils/theme'; /** @@ -22,7 +22,7 @@ export class Textarea implements ComponentInterface { private inputId = `ion-textarea-${textareaIds++}`; private didBlurAfterEdit = false; private textareaWrapper?: HTMLElement; - private inheritedAttributes: { [k: string]: any } = {}; + private inheritedAttributes: Attributes = {}; /** * This is required for a WebKit bug which requires us to diff --git a/core/src/utils/helpers.ts b/core/src/utils/helpers.ts index 6f977dc9c74..2e34e38a0c3 100644 --- a/core/src/utils/helpers.ts +++ b/core/src/utils/helpers.ts @@ -75,6 +75,8 @@ export const componentOnReady = (el: any, callback: any) => { } } +export type Attributes = { [key: string]: any }; + /** * Elements inside of web components sometimes need to inherit global attributes * set on the host. For example, the inner input in `ion-input` should inherit @@ -86,7 +88,7 @@ export const componentOnReady = (el: any, callback: any) => { * does not trigger a re-render. */ export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) => { - const attributeObject: { [k: string]: any } = {}; + const attributeObject: Attributes = {}; attributes.forEach(attr => { if (el.hasAttribute(attr)) { @@ -233,8 +235,8 @@ export const getAriaLabel = (componentEl: HTMLElement, inputId: string): { label labelText = label.textContent; label.setAttribute('aria-hidden', 'true'); - // if there is no label, check to see if the user has provided - // one by setting an id on the component and using the label element + // if there is no label, check to see if the user has provided + // one by setting an id on the component and using the label element } else if (componentId.trim() !== '') { label = document.querySelector(`label[for="${componentId}"]`); @@ -356,7 +358,7 @@ export const debounce = (func: (...args: any[]) => void, wait = 0) => { * * @returns whether the keys are the same and the values are shallow equal. */ -export const shallowEqualStringMap = (map1: {[k: string]: any} | undefined, map2: {[k: string]: any} | undefined): boolean => { +export const shallowEqualStringMap = (map1: { [k: string]: any } | undefined, map2: { [k: string]: any } | undefined): boolean => { map1 ??= {}; map2 ??= {}; @@ -380,4 +382,4 @@ export const shallowEqualStringMap = (map1: {[k: string]: any} | undefined, map2 } return true; -} \ No newline at end of file +}