Support vite2 (#3490)
* refactor: supports vite2 * refactor: remove CommonJS dependencies * chore: update pkg * refactor: update * chore: update pkgpull/3317/head
parent
75931a1b39
commit
f61018afd7
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* source by `component-classes`
|
||||
* https://github.com/component/classes.git
|
||||
*/
|
||||
|
||||
import { indexOf } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* Whitespace regexp.
|
||||
*/
|
||||
const re = /\s+/;
|
||||
|
||||
export class ClassList {
|
||||
el: Element;
|
||||
list: DOMTokenList;
|
||||
|
||||
constructor(el: Element) {
|
||||
if (!el || !el.nodeType) {
|
||||
throw new Error('A DOM element reference is required');
|
||||
}
|
||||
this.el = el;
|
||||
this.list = el.classList;
|
||||
}
|
||||
|
||||
array() {
|
||||
const className = this.el.getAttribute('class') || '';
|
||||
const str = className.replace(/^\s+|\s+$/g, '');
|
||||
const arr = str.split(re);
|
||||
if ('' === arr[0]) arr.shift();
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add class `name` if not already present.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {ClassList}
|
||||
* @api public
|
||||
*/
|
||||
add(name: string): ClassList {
|
||||
// classList
|
||||
if (this.list) {
|
||||
this.list.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
// fallback
|
||||
const arr = this.array();
|
||||
const i = indexOf(arr, name);
|
||||
if (!~i) arr.push(name);
|
||||
this.el.className = arr.join(' ');
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Remove class `name` when present, or
|
||||
* pass a regular expression to remove
|
||||
* any which match.
|
||||
*
|
||||
* @param {String|RegExp} name
|
||||
* @return {ClassList}
|
||||
* @api public
|
||||
*/
|
||||
remove(name: string | RegExp): ClassList {
|
||||
if ('[object RegExp]' === toString.call(name)) {
|
||||
return this._removeMatching(name as RegExp);
|
||||
}
|
||||
|
||||
// classList
|
||||
if (this.list) {
|
||||
this.list.remove(name as string);
|
||||
return this;
|
||||
}
|
||||
|
||||
// fallback
|
||||
const arr = this.array();
|
||||
const i = indexOf(arr, name);
|
||||
if (~i) arr.splice(i, 1);
|
||||
this.el.className = arr.join(' ');
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Remove all classes matching `re`.
|
||||
*
|
||||
* @param {RegExp} re
|
||||
* @return {ClassList}
|
||||
* @api private
|
||||
*/
|
||||
_removeMatching(re: RegExp): ClassList {
|
||||
const arr = this.array();
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (re.test(arr[i])) {
|
||||
this.remove(arr[i]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle class `name`, can force state via `force`.
|
||||
*
|
||||
* For browsers that support classList, but do not support `force` yet,
|
||||
* the mistake will be detected and corrected.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Boolean} force
|
||||
* @return {ClassList}
|
||||
* @api public
|
||||
*/
|
||||
toggle(name: string, force: boolean): ClassList {
|
||||
// classList
|
||||
if (this.list) {
|
||||
if ('undefined' !== typeof force) {
|
||||
if (force !== this.list.toggle(name, force)) {
|
||||
this.list.toggle(name); // toggle again to correct
|
||||
}
|
||||
} else {
|
||||
this.list.toggle(name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// fallback
|
||||
if ('undefined' !== typeof force) {
|
||||
if (!force) {
|
||||
this.remove(name);
|
||||
} else {
|
||||
this.add(name);
|
||||
}
|
||||
} else {
|
||||
if (this.has(name)) {
|
||||
this.remove(name);
|
||||
} else {
|
||||
this.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Check if class `name` is present.
|
||||
*
|
||||
* @param {String} name
|
||||
* @api public
|
||||
*/
|
||||
has(name: string) {
|
||||
return this.list ? this.list.contains(name) : !!~indexOf(this.array(), name);
|
||||
}
|
||||
/**
|
||||
* Check if class `name` is present.
|
||||
*
|
||||
* @param {String} name
|
||||
* @api public
|
||||
*/
|
||||
contains(name: string) {
|
||||
return this.has(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap `el` in a `ClassList`.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @return {ClassList}
|
||||
* @api public
|
||||
*/
|
||||
export default function(el: Element): ClassList {
|
||||
return new ClassList(el);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// https://github.com/yiminghe/css-animation 1.5.0
|
||||
|
||||
import Event from './Event';
|
||||
import classes from 'component-classes';
|
||||
import classes from '../component-classes';
|
||||
import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout';
|
||||
|
||||
const isCssAnimationSupported = Event.endEvents.length !== 0;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* source by `dom-closest`
|
||||
* https://github.com/necolas/dom-closest.git
|
||||
*/
|
||||
|
||||
import matches from './dom-matches';
|
||||
|
||||
/**
|
||||
* @param element {Element}
|
||||
* @param selector {String}
|
||||
* @param context {Element=}
|
||||
* @return {Element}
|
||||
*/
|
||||
export default function(element, selector, context) {
|
||||
context = context || document;
|
||||
// guard against orphans
|
||||
element = { parentNode: element };
|
||||
|
||||
while ((element = element.parentNode) && element !== context) {
|
||||
if (matches(element, selector)) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* source by `dom-matches`
|
||||
* https://github.com/necolas/dom-matches.git
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine if a DOM element matches a CSS selector
|
||||
*
|
||||
* @param {Element} elem
|
||||
* @param {String} selector
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export default function matches(elem, selector) {
|
||||
// Vendor-specific implementations of `Element.prototype.matches()`.
|
||||
const proto = window.Element.prototype;
|
||||
const nativeMatches =
|
||||
proto.matches ||
|
||||
proto.mozMatchesSelector ||
|
||||
proto.msMatchesSelector ||
|
||||
proto.oMatchesSelector ||
|
||||
proto.webkitMatchesSelector;
|
||||
|
||||
if (!elem || elem.nodeType !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentElem = elem.parentNode;
|
||||
|
||||
// use native 'matches'
|
||||
if (nativeMatches) {
|
||||
return nativeMatches.call(elem, selector);
|
||||
}
|
||||
|
||||
// native support for `matches` is missing and a fallback is required
|
||||
const nodes = parentElem.querySelectorAll(selector);
|
||||
const len = nodes.length;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (nodes[i] === elem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* source by `json2mq`
|
||||
* https://github.com/akiran/json2mq.git
|
||||
*/
|
||||
|
||||
const camel2hyphen = function(str) {
|
||||
return str
|
||||
.replace(/[A-Z]/g, function(match) {
|
||||
return '-' + match.toLowerCase();
|
||||
})
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const isDimension = function(feature) {
|
||||
const re = /[height|width]$/;
|
||||
return re.test(feature);
|
||||
};
|
||||
|
||||
const obj2mq = function(obj) {
|
||||
let mq = '';
|
||||
const features = Object.keys(obj);
|
||||
features.forEach(function(feature, index) {
|
||||
let value = obj[feature];
|
||||
feature = camel2hyphen(feature);
|
||||
// Add px to dimension features
|
||||
if (isDimension(feature) && typeof value === 'number') {
|
||||
value = value + 'px';
|
||||
}
|
||||
if (value === true) {
|
||||
mq += feature;
|
||||
} else if (value === false) {
|
||||
mq += 'not ' + feature;
|
||||
} else {
|
||||
mq += '(' + feature + ': ' + value + ')';
|
||||
}
|
||||
if (index < features.length - 1) {
|
||||
mq += ' and ';
|
||||
}
|
||||
});
|
||||
return mq;
|
||||
};
|
||||
|
||||
export default function(query) {
|
||||
let mq = '';
|
||||
if (typeof query === 'string') {
|
||||
return query;
|
||||
}
|
||||
// Handling array of media queries
|
||||
if (query instanceof Array) {
|
||||
query.forEach(function(q, index) {
|
||||
mq += obj2mq(q);
|
||||
if (index < query.length - 1) {
|
||||
mq += ', ';
|
||||
}
|
||||
});
|
||||
return mq;
|
||||
}
|
||||
// Handling single media query
|
||||
return obj2mq(query);
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import cssAnimation from './css-animation';
|
||||
import raf from 'raf';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
function animate(node, show, done) {
|
||||
|
@ -9,7 +8,7 @@ function animate(node, show, done) {
|
|||
return cssAnimation(node, 'ant-motion-collapse-legacy', {
|
||||
start() {
|
||||
if (appearRequestAnimationFrameId) {
|
||||
raf.cancel(appearRequestAnimationFrameId);
|
||||
cancelAnimationFrame(appearRequestAnimationFrameId);
|
||||
}
|
||||
if (!show) {
|
||||
node.style.height = `${node.offsetHeight}px`;
|
||||
|
@ -19,7 +18,7 @@ function animate(node, show, done) {
|
|||
// not get offsetHeight when appear
|
||||
// set it into raf get correct offsetHeight
|
||||
if (height === 0) {
|
||||
appearRequestAnimationFrameId = raf(() => {
|
||||
appearRequestAnimationFrameId = requestAnimationFrame(() => {
|
||||
height = node.offsetHeight;
|
||||
node.style.height = '0px';
|
||||
node.style.opacity = '0';
|
||||
|
@ -32,19 +31,19 @@ function animate(node, show, done) {
|
|||
},
|
||||
active() {
|
||||
if (requestAnimationFrameId) {
|
||||
raf.cancel(requestAnimationFrameId);
|
||||
cancelAnimationFrame(requestAnimationFrameId);
|
||||
}
|
||||
requestAnimationFrameId = raf(() => {
|
||||
requestAnimationFrameId = requestAnimationFrame(() => {
|
||||
node.style.height = `${show ? height : 0}px`;
|
||||
node.style.opacity = show ? '1' : '0';
|
||||
});
|
||||
},
|
||||
end() {
|
||||
if (appearRequestAnimationFrameId) {
|
||||
raf.cancel(appearRequestAnimationFrameId);
|
||||
cancelAnimationFrame(appearRequestAnimationFrameId);
|
||||
}
|
||||
if (requestAnimationFrameId) {
|
||||
raf.cancel(requestAnimationFrameId);
|
||||
cancelAnimationFrame(requestAnimationFrameId);
|
||||
}
|
||||
node.style.height = '';
|
||||
node.style.opacity = '';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import raf from 'raf';
|
||||
|
||||
interface RafMap {
|
||||
[id: number]: number;
|
||||
}
|
||||
|
@ -19,11 +17,11 @@ export default function wrapperRaf(callback: () => void, delayFrames = 1): numbe
|
|||
callback();
|
||||
delete ids[myId];
|
||||
} else {
|
||||
ids[myId] = raf(internalCallback);
|
||||
ids[myId] = requestAnimationFrame(internalCallback);
|
||||
}
|
||||
}
|
||||
|
||||
ids[myId] = raf(internalCallback);
|
||||
ids[myId] = requestAnimationFrame(internalCallback);
|
||||
|
||||
return myId;
|
||||
}
|
||||
|
@ -31,7 +29,7 @@ export default function wrapperRaf(callback: () => void, delayFrames = 1): numbe
|
|||
wrapperRaf.cancel = function cancel(pid?: number) {
|
||||
if (pid === undefined) return;
|
||||
|
||||
raf.cancel(ids[pid]);
|
||||
cancelAnimationFrame(ids[pid]);
|
||||
delete ids[pid];
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import raf from 'raf';
|
||||
import getScroll from './getScroll';
|
||||
import { easeInOutCubic } from './easings';
|
||||
|
||||
|
@ -28,10 +27,10 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) {
|
|||
(container as HTMLElement).scrollTop = nextScrollTop;
|
||||
}
|
||||
if (time < duration) {
|
||||
raf(frameFunc);
|
||||
requestAnimationFrame(frameFunc);
|
||||
} else if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
raf(frameFunc);
|
||||
requestAnimationFrame(frameFunc);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,50 @@
|
|||
import shallowequal from 'shallowequal';
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
export default function(value, other, customizer, thisArg) {
|
||||
return shallowequal(toRaw(value), toRaw(other), customizer, thisArg);
|
||||
function shallowEqual(objA, objB, compare, compareContext) {
|
||||
let ret = compare ? compare.call(compareContext, objA, objB) : void 0;
|
||||
|
||||
if (ret !== void 0) {
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
if (objA === objB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof objA !== 'object' || !objA || typeof objB !== 'object' || !objB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
|
||||
|
||||
// Test for A's keys different from B.
|
||||
for (let idx = 0; idx < keysA.length; idx++) {
|
||||
const key = keysA[idx];
|
||||
|
||||
if (!bHasOwnProperty(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const valueA = objA[key];
|
||||
const valueB = objB[key];
|
||||
|
||||
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
|
||||
|
||||
if (ret === false || (ret === void 0 && valueA !== valueB)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function(value, other, customizer, thisArg) {
|
||||
return shallowEqual(toRaw(value), toRaw(other), customizer, thisArg);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import raf from 'raf';
|
||||
|
||||
export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
|
||||
let requestId: number | null;
|
||||
|
||||
|
@ -10,11 +8,11 @@ export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
|
|||
|
||||
const throttled = (...args: any[]) => {
|
||||
if (requestId == null) {
|
||||
requestId = raf(later(args));
|
||||
requestId = requestAnimationFrame(later(args));
|
||||
}
|
||||
};
|
||||
|
||||
(throttled as any).cancel = () => raf.cancel(requestId!);
|
||||
(throttled as any).cancel = () => cancelAnimationFrame(requestId!);
|
||||
|
||||
return throttled;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reactive, defineComponent, nextTick, computed, watch } from 'vue';
|
||||
import FilterFilled from '@ant-design/icons-vue/FilterFilled';
|
||||
import Menu, { SubMenu, Item as MenuItem } from '../vc-menu';
|
||||
import closest from 'dom-closest';
|
||||
import closest from '../_util/dom-closest';
|
||||
import classNames from '../_util/classNames';
|
||||
import shallowequal from '../_util/shallowequal';
|
||||
import Dropdown from '../dropdown';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import json2mq from 'json2mq';
|
||||
import json2mq from '../../_util/json2mq';
|
||||
import BaseMixin from '../../_util/BaseMixin';
|
||||
import { cloneElement } from '../../_util/vnode';
|
||||
import InnerSlider from './inner-slider';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { provide, markRaw, defineComponent } from 'vue';
|
||||
import shallowequal from '../../_util/shallowequal';
|
||||
import merge from 'lodash-es/merge';
|
||||
import classes from 'component-classes';
|
||||
import classes from '../../_util/component-classes';
|
||||
import classNames from '../../_util/classNames';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { debounce, getDataAndAriaProps } from './utils';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { defineComponent, provide, reactive, watchEffect } from 'vue';
|
||||
import BaseMixin from '../../_util/BaseMixin';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import raf from 'raf';
|
||||
import KeyCode from './KeyCode';
|
||||
import { cloneElement } from '../../_util/vnode';
|
||||
import Sentinel from './Sentinel';
|
||||
|
@ -80,7 +79,7 @@ export default defineComponent({
|
|||
},
|
||||
beforeUnmount() {
|
||||
this.destroy = true;
|
||||
raf.cancel(this.sentinelId);
|
||||
cancelAnimationFrame(this.sentinelId);
|
||||
},
|
||||
methods: {
|
||||
onTabClick(activeKey, e) {
|
||||
|
@ -171,8 +170,8 @@ export default defineComponent({
|
|||
updateSentinelContext() {
|
||||
if (this.destroy) return;
|
||||
|
||||
raf.cancel(this.sentinelId);
|
||||
this.sentinelId = raf(() => {
|
||||
cancelAnimationFrame(this.sentinelId);
|
||||
this.sentinelId = requestAnimationFrame(() => {
|
||||
if (this.destroy) return;
|
||||
this.$forceUpdate();
|
||||
});
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import PropTypes from '../_util/vue-types';
|
||||
import BaseMixin from '../_util/BaseMixin';
|
||||
import classnames from '../_util/classNames';
|
||||
import raf from 'raf';
|
||||
import { findDOMNode } from '../_util/props-util';
|
||||
|
||||
function noop() {}
|
||||
const scrollTo = (element, to, duration) => {
|
||||
// jump to target if duration zero
|
||||
if (duration <= 0) {
|
||||
raf(() => {
|
||||
requestAnimationFrame(() => {
|
||||
element.scrollTop = to;
|
||||
});
|
||||
return;
|
||||
|
@ -16,7 +15,7 @@ const scrollTo = (element, to, duration) => {
|
|||
const difference = to - element.scrollTop;
|
||||
const perTick = (difference / duration) * 10;
|
||||
|
||||
raf(() => {
|
||||
requestAnimationFrame(() => {
|
||||
element.scrollTop += perTick;
|
||||
if (element.scrollTop === to) return;
|
||||
scrollTo(element, to, duration - 10);
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
*/
|
||||
import { defineComponent, provide } from 'vue';
|
||||
import shallowEqual from '../../_util/shallowequal';
|
||||
import raf from 'raf';
|
||||
import scrollIntoView from 'dom-scroll-into-view';
|
||||
import warning from 'warning';
|
||||
import PropTypes, { withUndefined } from '../../_util/vue-types';
|
||||
|
@ -227,7 +226,7 @@ const Select = defineComponent({
|
|||
|
||||
if (treeNode) {
|
||||
const domNode = findDOMNode(treeNode);
|
||||
raf(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const popupNode = findDOMNode(this.popup);
|
||||
const triggerContainer = findPopupContainer(popupNode, `${prefixCls}-dropdown`);
|
||||
|
||||
|
@ -886,7 +885,7 @@ const Select = defineComponent({
|
|||
},
|
||||
|
||||
onChoiceAnimationLeave() {
|
||||
raf(() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.forcePopupAlign();
|
||||
});
|
||||
},
|
||||
|
@ -963,8 +962,8 @@ const Select = defineComponent({
|
|||
delayForcePopupAlign() {
|
||||
// Wait 2 frame to avoid dom update & dom algin in the same time
|
||||
// https://github.com/ant-design/ant-design/issues/12031
|
||||
raf(() => {
|
||||
raf(this.forcePopupAlign);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(this.forcePopupAlign);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
14
package.json
14
package.json
|
@ -201,35 +201,25 @@
|
|||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-merge": "^4.1.1",
|
||||
"webpackbar": "^4.0.0",
|
||||
"xhr-mock": "^2.5.1"
|
||||
"xhr-mock": "^2.5.1",
|
||||
"node-emoji": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design-vue/use": "^0.0.1-0",
|
||||
"@ant-design/icons-vue": "^5.1.7",
|
||||
"@babel/runtime": "^7.10.5",
|
||||
"@simonwep/pickr": "~1.7.0",
|
||||
"add-dom-event-listener": "^1.0.2",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"async-validator": "^3.3.0",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"component-classes": "^1.2.6",
|
||||
"dom-align": "^1.10.4",
|
||||
"dom-closest": "^0.2.0",
|
||||
"dom-scroll-into-view": "^2.0.0",
|
||||
"intersperse": "^1.0.0",
|
||||
"is-mobile": "^2.2.1",
|
||||
"is-negative-zero": "^2.0.0",
|
||||
"ismobilejs": "^1.0.0",
|
||||
"json2mq": "^0.2.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"moment": "^2.27.0",
|
||||
"node-emoji": "^1.10.0",
|
||||
"omit.js": "^2.0.0",
|
||||
"raf": "^3.4.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"scroll-into-view-if-needed": "^2.2.25",
|
||||
"shallow-equal": "^1.0.0",
|
||||
"shallowequal": "^1.0.2",
|
||||
"vue-types": "^3.0.0",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
declare module 'component-classes';
|
||||
declare module 'omit.js';
|
||||
|
||||
declare module '*.json' {
|
||||
|
|
Loading…
Reference in New Issue