@v-c/image 1.0.7 → 1.0.9

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/Image.d.ts CHANGED
@@ -39,8 +39,10 @@ export interface ImageProps extends Partial<Omit<ImageElementProps, 'src'>> {
39
39
  preview?: boolean | PreviewConfig;
40
40
  onClick?: (e: MouseEvent) => void;
41
41
  onError?: (e: Event) => void;
42
+ onKeydown?: (e: KeyboardEvent) => void;
42
43
  width?: string | number;
43
44
  height?: string | number;
45
+ fetchPriority?: HTMLImageElement['fetchPriority'];
44
46
  }
45
47
  declare const Image: import('vue').DefineSetupFnComponent<ImageProps, {}, {}, ImageProps & {}, import('vue').PublicProps>;
46
48
  export default Image;
package/dist/Image.js CHANGED
@@ -69,6 +69,24 @@ var Image_default = /* @__PURE__ */ defineComponent((props, { attrs, slots }) =>
69
69
  const onImgError = (e) => {
70
70
  props.onError?.(e);
71
71
  };
72
+ const onPreviewKeyDown = (event) => {
73
+ props.onKeydown?.(event);
74
+ if (!canPreview) return;
75
+ if (event.key === "Enter" || event.key === " ") {
76
+ event.preventDefault();
77
+ const rect = event.target.getBoundingClientRect();
78
+ const left = rect.x + rect.width / 2;
79
+ const top = rect.y + rect.height / 2;
80
+ if (groupContext) groupContext.onPreview(imageId, src.value || "", left, top);
81
+ else {
82
+ mousePosition.value = {
83
+ x: left,
84
+ y: top
85
+ };
86
+ triggerPreviewOpen(true);
87
+ }
88
+ }
89
+ };
72
90
  return () => {
73
91
  const { width, height } = props;
74
92
  const { className, style: attrStyle, restAttrs } = getAttrStyleAndClass(attrs);
@@ -96,6 +114,10 @@ var Image_default = /* @__PURE__ */ defineComponent((props, { attrs, slots }) =>
96
114
  return createVNode(Fragment, null, [createVNode("div", mergeProps(pickAttrs(restAttrs, false), {
97
115
  "class": rootCls,
98
116
  "onClick": canPreview.value ? onPreview : onInternalClick,
117
+ "role": canPreview ? "button" : restAttrs.role,
118
+ "tabindex": canPreview && restAttrs.tabIndex == null ? 0 : restAttrs.tabIndex,
119
+ "aria-label": canPreview ? restAttrs["aria-label"] ?? restAttrs.alt : restAttrs["aria-label"],
120
+ "onKeydown": onPreviewKeyDown,
99
121
  "style": rootStyle
100
122
  }), [
101
123
  createVNode("img", mergeProps(imgCommonProps.value, {
@@ -203,6 +225,11 @@ var Image_default = /* @__PURE__ */ defineComponent((props, { attrs, slots }) =>
203
225
  required: false,
204
226
  default: void 0
205
227
  },
228
+ onKeydown: {
229
+ type: Function,
230
+ required: false,
231
+ default: void 0
232
+ },
206
233
  width: {
207
234
  type: [String, Number],
208
235
  required: false,
@@ -212,6 +239,10 @@ var Image_default = /* @__PURE__ */ defineComponent((props, { attrs, slots }) =>
212
239
  type: [String, Number],
213
240
  required: false,
214
241
  default: void 0
242
+ },
243
+ fetchPriority: {
244
+ required: false,
245
+ default: void 0
215
246
  }
216
247
  }, {
217
248
  prefixCls: "vc-image",
@@ -3,12 +3,15 @@ import { clsx } from "@v-c/util";
3
3
  var Footer_default = /* @__PURE__ */ defineComponent((props) => {
4
4
  const renderOperation = ({ type, disabled, onClick, icon }) => {
5
5
  const actionCls = `${props.prefixCls}-actions-action`;
6
- return createVNode("div", {
6
+ return createVNode("button", {
7
+ "type": "button",
7
8
  "key": type,
8
9
  "class": clsx(actionCls, `${actionCls}-${type}`, { [`${actionCls}-disabled`]: !!disabled }),
9
10
  "onClick": () => {
10
11
  if (!disabled) onClick();
11
- }
12
+ },
13
+ "disabled": !!disabled,
14
+ "aria-label": type
12
15
  }, [icon]);
13
16
  };
14
17
  return () => {
@@ -6,17 +6,19 @@ var PrevNext_default = /* @__PURE__ */ defineComponent((props) => {
6
6
  const switchCls = `${prefixCls}-switch`;
7
7
  const prevIcon = icons.prev ?? icons.left;
8
8
  const nextIcon = icons.next ?? icons.right;
9
- const isPrevDisabled = current === 0;
10
- const isNextDisabled = current === count - 1;
11
- return createVNode(Fragment, null, [createVNode("div", {
12
- "class": clsx(switchCls, `${switchCls}-prev`, { [`${switchCls}-disabled`]: isPrevDisabled }),
9
+ const prevDisabled = current === 0;
10
+ const nextDisabled = current === count - 1;
11
+ return createVNode(Fragment, null, [createVNode("button", {
12
+ "type": "button",
13
+ "class": clsx(switchCls, `${switchCls}-prev`, { [`${switchCls}-disabled`]: prevDisabled }),
13
14
  "onClick": () => {
14
- if (!isPrevDisabled) onActive(-1);
15
+ if (!prevDisabled) onActive(-1);
15
16
  }
16
- }, [prevIcon]), createVNode("div", {
17
- "class": clsx(switchCls, `${switchCls}-next`, { [`${switchCls}-disabled`]: isNextDisabled }),
17
+ }, [prevIcon]), createVNode("button", {
18
+ "type": "button",
19
+ "class": clsx(switchCls, `${switchCls}-next`, { [`${switchCls}-disabled`]: nextDisabled }),
18
20
  "onClick": () => {
19
- if (!isNextDisabled) onActive(1);
21
+ if (!nextDisabled) onActive(1);
20
22
  }
21
23
  }, [nextIcon])]);
22
24
  };
@@ -1,6 +1,7 @@
1
1
  import { usePreviewGroupContext } from "../context.js";
2
2
  import useStatus from "../hooks/useStatus.js";
3
3
  import { canUseDom } from "../util/dist/Dom/canUseDom.js";
4
+ import { useLockFocus } from "../util/dist/Dom/focus.js";
4
5
  import { KeyCodeStr } from "../util/dist/KeyCode.js";
5
6
  import { getTransitionProps } from "../util/dist/utils/transition.js";
6
7
  import useImageTransform from "../hooks/useImageTransform.js";
@@ -15,6 +16,7 @@ import { clsx } from "@v-c/util";
15
16
  import Portal from "@v-c/portal";
16
17
  var Preview_default = /* @__PURE__ */ defineComponent((props, { attrs, slots }) => {
17
18
  const imgEl = shallowRef();
19
+ const wrapperRef = shallowRef(null);
18
20
  const groupContext = usePreviewGroupContext();
19
21
  const showLeftOrRightSwitches = computed(() => !!groupContext && (props.count ?? 1) > 1);
20
22
  const showOperationsProgress = computed(() => !!groupContext && (props.count ?? 1) >= 1);
@@ -112,8 +114,9 @@ var Preview_default = /* @__PURE__ */ defineComponent((props, { attrs, slots })
112
114
  src: computed(() => props.src),
113
115
  fallback: computed(() => props.fallback)
114
116
  });
117
+ useLockFocus(portalRender, () => wrapperRef.value);
115
118
  return () => {
116
- const { prefixCls, rootClassName, src, alt, imageInfo, open, closeIcon, getContainer, current = 0, count = 1, countRender, motionName = "fade", imageRender, imgCommonProps, actionsRender = slots?.actionsRender, classNames = {}, styles = {}, mousePosition, zIndex, icons = {} } = props;
119
+ const { prefixCls, rootClassName, src, alt, imageInfo, open, closeIcon, getContainer, current = 0, count = 1, countRender, motionName = "fade", imageRender, imgCommonProps, actionsRender = slots?.actionsRender, classNames = {}, styles = {}, mousePosition, zIndex, icons = {}, movable = true } = props;
117
120
  const bodyStyle = { ...styles.body ?? {} };
118
121
  if (mousePosition) bodyStyle.transformOrigin = `${mousePosition.x}px ${mousePosition.y}px`;
119
122
  const image = {
@@ -149,7 +152,10 @@ var Preview_default = /* @__PURE__ */ defineComponent((props, { attrs, slots })
149
152
  ...attrs.style
150
153
  };
151
154
  if (zIndex) mergedRootStyle.zIndex = zIndex;
152
- const mergedRootCls = clsx(prefixCls, rootClassName, classNames.root, { [`${prefixCls}-moving`]: isMoving.value });
155
+ const mergedRootCls = clsx(prefixCls, rootClassName, classNames.root, {
156
+ [`${prefixCls}-movable`]: movable,
157
+ [`${prefixCls}-moving`]: isMoving.value
158
+ });
153
159
  const transitionProps = getTransitionProps(motionName);
154
160
  return createVNode(Portal, {
155
161
  "open": open || animatedVisible.value || portalRender.value,
@@ -161,8 +167,13 @@ var Preview_default = /* @__PURE__ */ defineComponent((props, { attrs, slots })
161
167
  }), { default: () => {
162
168
  if (!(portalRender.value && open)) return null;
163
169
  return createVNode("div", {
170
+ "ref": wrapperRef,
164
171
  "class": mergedRootCls,
165
- "style": mergedRootStyle
172
+ "style": mergedRootStyle,
173
+ "role": "dialog",
174
+ "aria-modal": "true",
175
+ "aria-label": alt,
176
+ "tabindex": -1
166
177
  }, [
167
178
  createVNode("div", {
168
179
  "class": clsx(`${prefixCls}-mask`, classNames.mask),
package/dist/common.js CHANGED
@@ -7,6 +7,7 @@ const COMMON_PROPS = [
7
7
  "sizes",
8
8
  "srcset",
9
9
  "useMap",
10
- "alt"
10
+ "alt",
11
+ "fetchPriority"
11
12
  ];
12
13
  export { COMMON_PROPS };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Used for PreviewGroup passed image data
3
3
  */
4
- export type ImageElementProps = Pick<HTMLImageElement, 'src' | 'crossOrigin' | 'decoding' | 'draggable' | 'loading' | 'referrerPolicy' | 'sizes' | 'srcset' | 'useMap' | 'alt'>;
4
+ export type ImageElementProps = Pick<HTMLImageElement, 'src' | 'crossOrigin' | 'decoding' | 'draggable' | 'loading' | 'referrerPolicy' | 'sizes' | 'srcset' | 'useMap' | 'alt' | 'fetchPriority'>;
5
5
  export interface PreviewImageElementProps {
6
6
  data: ImageElementProps;
7
7
  canPreview: boolean;
@@ -0,0 +1,20 @@
1
+ import { canUseDom } from "./canUseDom.js";
2
+ import { unref } from "vue";
3
+ function isDOM(node) {
4
+ return node instanceof HTMLElement || node instanceof SVGElement;
5
+ }
6
+ function getDOM(elementRef) {
7
+ const unrefElementRef = unref(elementRef);
8
+ if (!canUseDom()) return unrefElementRef;
9
+ const dom = findDOMNode(unrefElementRef) || (unrefElementRef && typeof unrefElementRef === "object" ? findDOMNode(unrefElementRef.nativeElement) : null);
10
+ if (dom && (dom.nodeType === 3 || dom.nodeType === 8) && dom.nextElementSibling) return dom.nextElementSibling;
11
+ return dom;
12
+ }
13
+ function findDOMNode(_node) {
14
+ const node = unref(_node);
15
+ if (!canUseDom()) return node;
16
+ if (isDOM(node)) return node;
17
+ else if (node && "$el" in node) return node.$el;
18
+ return null;
19
+ }
20
+ export { getDOM };
@@ -0,0 +1,129 @@
1
+ import { useId_default } from "../hooks/useId.js";
2
+ import { getDOM } from "./findDOMNode.js";
3
+ import { isVisible_default } from "./isVisible.js";
4
+ import { watch } from "vue";
5
+ function focusable(node, includePositive = false) {
6
+ if (isVisible_default(node)) {
7
+ const nodeName = node.nodeName.toLowerCase();
8
+ const isFocusableElement = [
9
+ "input",
10
+ "select",
11
+ "textarea",
12
+ "button"
13
+ ].includes(nodeName) || node.isContentEditable || nodeName === "a" && !!node.getAttribute("href");
14
+ const tabIndexAttr = node.getAttribute("tabindex");
15
+ const tabIndexNum = Number(tabIndexAttr);
16
+ let tabIndex = null;
17
+ if (tabIndexAttr && !Number.isNaN(tabIndexNum)) tabIndex = tabIndexNum;
18
+ else if (isFocusableElement && tabIndex === null) tabIndex = 0;
19
+ if (isFocusableElement && node.disabled) tabIndex = null;
20
+ return tabIndex !== null && (tabIndex >= 0 || includePositive && tabIndex < 0);
21
+ }
22
+ return false;
23
+ }
24
+ function getFocusNodeList(node, includePositive = false) {
25
+ const res = [...node.querySelectorAll("*")].filter((child) => {
26
+ return focusable(child, includePositive);
27
+ });
28
+ if (focusable(node, includePositive)) res.unshift(node);
29
+ return res;
30
+ }
31
+ var lastFocusElement = null;
32
+ var focusElements = [];
33
+ var idToElementMap = /* @__PURE__ */ new Map();
34
+ var ignoredElementMap = /* @__PURE__ */ new Map();
35
+ var allowedElementMap = /* @__PURE__ */ new Map();
36
+ function getLastElement() {
37
+ return focusElements[focusElements.length - 1];
38
+ }
39
+ function getLastLockId() {
40
+ const lastElement = getLastElement();
41
+ if (!lastElement) return void 0;
42
+ for (const [id, ele] of idToElementMap.entries()) if (ele === lastElement) return id;
43
+ }
44
+ function isIgnoredElement(element) {
45
+ const lockId = getLastLockId();
46
+ if (!lockId || !element) return false;
47
+ const ignoredEle = ignoredElementMap.get(lockId);
48
+ return !!ignoredEle && (ignoredEle === element || ignoredEle.contains(element));
49
+ }
50
+ function isAllowedElement(element) {
51
+ const lockId = getLastLockId();
52
+ if (!lockId || !element) return false;
53
+ const allowedElements = allowedElementMap.get(lockId);
54
+ if (!allowedElements?.size) return false;
55
+ for (const allowedElement of allowedElements) if (allowedElement === element || allowedElement.contains(element)) return true;
56
+ return false;
57
+ }
58
+ function hasFocus(element) {
59
+ const { activeElement } = document;
60
+ return element === activeElement || element.contains(activeElement);
61
+ }
62
+ function syncFocus() {
63
+ const lastElement = getLastElement();
64
+ const { activeElement } = document;
65
+ if (isIgnoredElement(activeElement) || isAllowedElement(activeElement)) return;
66
+ if (lastElement && !hasFocus(lastElement)) {
67
+ const focusableList = getFocusNodeList(lastElement);
68
+ (focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0])?.focus({ preventScroll: true });
69
+ } else lastFocusElement = activeElement;
70
+ }
71
+ function onWindowKeyDown(e) {
72
+ if (e.key === "Tab") {
73
+ const { activeElement } = document;
74
+ const focusableList = getFocusNodeList(getLastElement());
75
+ const last = focusableList[focusableList.length - 1];
76
+ if (e.shiftKey && activeElement === focusableList[0]) lastFocusElement = last;
77
+ else if (!e.shiftKey && activeElement === last) lastFocusElement = focusableList[0];
78
+ }
79
+ }
80
+ function lockFocus(element, id) {
81
+ if (element) {
82
+ idToElementMap.set(id, element);
83
+ focusElements = focusElements.filter((ele) => ele !== element);
84
+ focusElements.push(element);
85
+ window.addEventListener("focusin", syncFocus);
86
+ window.addEventListener("keydown", onWindowKeyDown, true);
87
+ syncFocus();
88
+ }
89
+ return () => {
90
+ lastFocusElement = null;
91
+ focusElements = focusElements.filter((ele) => ele !== element);
92
+ idToElementMap.delete(id);
93
+ ignoredElementMap.delete(id);
94
+ allowedElementMap.delete(id);
95
+ if (focusElements.length === 0) {
96
+ window.removeEventListener("focusin", syncFocus);
97
+ window.removeEventListener("keydown", onWindowKeyDown, true);
98
+ }
99
+ };
100
+ }
101
+ function useLockFocus(lock, getElement) {
102
+ const id = useId_default();
103
+ watch([lock, () => getElement()], ([nextLock, element], _o, onCleanup) => {
104
+ element = getDOM(element);
105
+ if (nextLock && element) onCleanup(lockFocus(element, id));
106
+ }, {
107
+ flush: "post",
108
+ immediate: true
109
+ });
110
+ const ignoreElement = (ele) => {
111
+ if (ele) ignoredElementMap.set(id, ele);
112
+ };
113
+ const registerAllowedElement = (ele) => {
114
+ if (!ele) return () => {};
115
+ let allowedElements = allowedElementMap.get(id);
116
+ if (!allowedElements) {
117
+ allowedElements = /* @__PURE__ */ new Set();
118
+ allowedElementMap.set(id, allowedElements);
119
+ }
120
+ allowedElements.add(ele);
121
+ return () => {
122
+ const nextAllowedElements = allowedElementMap.get(id);
123
+ nextAllowedElements?.delete(ele);
124
+ if (!nextAllowedElements?.size) allowedElementMap.delete(id);
125
+ };
126
+ };
127
+ return [ignoreElement, registerAllowedElement];
128
+ }
129
+ export { useLockFocus };
@@ -0,0 +1,16 @@
1
+ var isVisible_default = (element) => {
2
+ if (!element) return false;
3
+ if (element instanceof Element) {
4
+ if (element.offsetParent) return true;
5
+ if (element.getBBox) {
6
+ const { width, height } = element.getBBox();
7
+ if (width || height) return true;
8
+ }
9
+ if (element.getBoundingClientRect) {
10
+ const { width, height } = element.getBoundingClientRect();
11
+ if (width || height) return true;
12
+ }
13
+ }
14
+ return false;
15
+ };
16
+ export { isVisible_default };
@@ -0,0 +1,12 @@
1
+ import { useId } from "vue";
2
+ function getUseId() {
3
+ return useId;
4
+ }
5
+ var useOriginalId = getUseId();
6
+ function useId_default(id) {
7
+ const vueId = useOriginalId();
8
+ if (id) return id;
9
+ if (process.env.NODE_ENV === "test") return "test-id";
10
+ return vueId;
11
+ }
12
+ export { useId_default };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@v-c/image",
3
3
  "type": "module",
4
- "version": "1.0.7",
4
+ "version": "1.0.9",
5
5
  "description": "image component",
6
6
  "author": "",
7
7
  "license": "MIT",
@@ -32,8 +32,8 @@
32
32
  "vue": "^3.0.0"
33
33
  },
34
34
  "dependencies": {
35
- "@v-c/util": "^1.0.19",
36
- "@v-c/portal": "^1.0.8"
35
+ "@v-c/portal": "^1.0.8",
36
+ "@v-c/util": "^1.0.19"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@ant-design/icons-vue": "^7.0.1"