diff --git a/devui/datepicker/components/calendar/components/panel/index.css b/devui/datepicker/components/calendar/components/panel/index.css new file mode 100644 index 0000000000000000000000000000000000000000..632c28233a512fab565ceae6ec58cea07312c12d --- /dev/null +++ b/devui/datepicker/components/calendar/components/panel/index.css @@ -0,0 +1,74 @@ + +.calendar-panel { + padding: 5px; + width: 300px; + box-sizing: border-box; +} + +.calendar-panel-row { + display: flex; + flex-direction: row; + justify-content: space-between; + height: 32px; +} + +.calendar-panel-cell { + width: 100%; + text-align: center; + flex-grow: 1; + flex-shrink: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.calendar-panel-body .calendar-panel-cell:hover { + background-color: #eeeeee; + color: #000000; +} + +.calendar-panel-cell.selected { + background-color: #0066cc; + color: #ffffff; + border-radius: 6px; +} + +.calendar-panel-cell.selected:hover { + background-color: #0066cc; + color: #ffffff; +} + +.calendar-panel-cell.innerday { + background-color: #f1f1f1; +} + +.calendar-panel-cell.disabled { + background-color: #ffffff; + color: #bbbbbb; +} + +.calendar-head-cell-text, +.calendar-panel-cell-text { + font-weight: normal; + font-style: normal; + font-size: 16px; + text-align: center; +} + +.calendar-panel-header { + height: 32px; + cursor: default; + padding: 0; + margin: 0; + list-style: none; +} + +.calendar-panel-body { + display: flex; + flex-direction: column; + cursor: pointer; + padding: 0; + margin: 0; + list-style: none; +} diff --git a/devui/datepicker/components/calendar/components/panel/index.tsx b/devui/datepicker/components/calendar/components/panel/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..84892677db0d3cad43c05d962fc33418bc1aaafe --- /dev/null +++ b/devui/datepicker/components/calendar/components/panel/index.tsx @@ -0,0 +1,40 @@ +import { TDatePanelProps } from '../../types' +import { getMonthWeeklyDays, WEEK_DAYS } from '../../utils' +import { handleDateEnter, cellClassName, trigEvent } from '../../helper' +import Toolbar from '../toolbar' +import './index.css' + +const CalendarDatePanel = (props: TDatePanelProps) => { + return ( +
+ +
    { + WEEK_DAYS.map(day =>
  1. {day}
  2. ) + }
+ +
+ ) +} + +export default CalendarDatePanel \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toolbar/icons.tsx b/devui/datepicker/components/calendar/components/toolbar/icons.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c6740da1a854723e54cfa09020557f61fb378d93 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/icons.tsx @@ -0,0 +1,27 @@ +export type TIconSvgProps = { + color?: string + rotate?: number +} +export type TIconSvg = (props: TIconSvgProps) => any + +export const Year = (props: TIconSvgProps) => { + const { color = '#585d6b', rotate = 0 } = props + return ( + + + + + + ) +} + +export const Month = (props: TIconSvgProps) => { + const { color = '#585d6b', rotate = 0 } = props + return ( + + + + + + ) +} \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toolbar/index.css b/devui/datepicker/components/calendar/components/toolbar/index.css new file mode 100644 index 0000000000000000000000000000000000000000..c017c4c26df9ce7d5cbee5d52da0fd94b2e228a0 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/index.css @@ -0,0 +1,19 @@ +.calendar-toolbar { + display: flex; + justify-content: space-between; + font-size: 16px; + align-items: center; + height: 36px; + user-select: none; + font-weight: bold; +} + +.calendar-toolbar a { + color: #000000; + text-decoration: none; + cursor: pointer; +} + +.calendar-toolbar a:hover { + color: #0066cc; +} diff --git a/devui/datepicker/components/calendar/components/toolbar/index.tsx b/devui/datepicker/components/calendar/components/toolbar/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8e8124e098cbee437fe984a6caa988a44da8af40 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/index.tsx @@ -0,0 +1,38 @@ +import { compareDate } from '../../utils' +import { Year, Month } from './icons' +import { TDateToolbarProps } from '../../types' +import Item, { CalendarToolbarTitle as Title } from './toolbar-item' +import './index.css' + +const CalendarToolbar = (props: TDateToolbarProps) => { + const { + type, current, compare, pos, + onPreviousYear, + onPreviousMonth, + onNextMonth, + onNextYear, + } = props + + const dis = [false, false, false, false] + if (type === 'range') { + if (pos === 1) { + dis[0] = !compareDate(compare, current, 'year', 1) + dis[1] = !compareDate(compare, current, 'month', 1) + } else { + dis[2] = !compareDate(current, compare, 'month', 1) + dis[3] = !compareDate(current, compare, 'year', 1) + } + } + + return ( +
+ + + + <Item disabled={dis[2]} date={current} pos={pos} button={Month} rotate={90} cb={onNextMonth} /> + <Item disabled={dis[3]} date={current} pos={pos} button={Year} rotate={180} cb={onNextYear} /> + </div> + ) +} + +export default CalendarToolbar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css new file mode 100644 index 0000000000000000000000000000000000000000..e207dcb3c7c32232c943272d17cbc227dc67a6a1 --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.css @@ -0,0 +1,24 @@ +.calendar-toolbar-button { + display: block; + width: 28px; + height: 28px; + line-height: 28px; + text-align: center; + border: 1px soild #000000; + flex-shrink: 0; + flex-grow: 0; +} + +.calendar-toolbar-button.title { + flex-grow: 1; + flex-shrink: 1; +} + +.calendar-toolbar-button.disabled { + color: #aaaaaa; + cursor: not-allowed; +} + +.calendar-toolbar-button.disabled:hover { + color: #aaaaaa; +} diff --git a/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5aae2be18cd424c4b7633b98d52c6f6289289dac --- /dev/null +++ b/devui/datepicker/components/calendar/components/toolbar/toolbar-item.tsx @@ -0,0 +1,42 @@ +import { invokeCallback } from '../../utils' +import { TIconSvg } from './icons' +import './toolbar-item.css' + +export type TCalendarToolbarItemProps = { + disabled?: boolean + rotate?: number + cb?: (...args: any[]) => void + pos: number + date: Date + button: TIconSvg +} + +const CalendarToolbarItem = (props: TCalendarToolbarItemProps) => { + const { + button: Btn, + disabled = false, + rotate = 0, + date, + pos, + cb, + } = props + const color = disabled ? '#cfd0d3' : '#585d6b' + const className = `calendar-toolbar-button ${disabled ? 'disabled' : ''}` + const handleClick = disabled ? undefined : () => invokeCallback(props.cb, date, pos) + return ( + <a className={className} onClick={handleClick}> + <Btn color={color} rotate={rotate} /> + </a> + ) +} + +export const CalendarToolbarTitle = (props: { date: Date; }) => { + const { date } = props + return ( + <a className="calendar-toolbar-button title">{ + `${date.getFullYear()}年${(date.getMonth() + 1 + '').padStart(2, '0')}月` + }</a> + ) +} + +export default CalendarToolbarItem \ No newline at end of file diff --git a/devui/datepicker/components/calendar/helper.ts b/devui/datepicker/components/calendar/helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..5463fb950fa62aa852c9eea2820c7e446a5024d2 --- /dev/null +++ b/devui/datepicker/components/calendar/helper.ts @@ -0,0 +1,67 @@ +import { invokeCallback, subDateDay } from './utils' +import { TDateCell, TDatePanelDataProps, TDatePanelProps } from './types' + +export const getDateKey = (date: Date): string => { + return date.toDateString() +} + +export const cellClassName = (props: TDatePanelDataProps, day: TDateCell, base = 'calendar-panel-cell'): string => { + if(day.current !== 0) { + return `${base} disabled` + } + const key = getDateKey(day.date) + if (props.type === 'range') { + if (props.dateStart) { + if (getDateKey(props.dateStart) === key) { + return `${base} selected` + } + if (props.dateEnd && getDateKey(props.dateEnd) === key) { + return `${base} selected` + } + const innerEnd = props.dateEnd || props.dateHover + if (innerEnd) { + const range = innerEnd > props.dateStart ? [props.dateStart, innerEnd] : [innerEnd, props.dateStart] + if (day.date > range[0] && day.date < range[1]) { + return `${base} innerday` + } + } + } + return base + } else { + return props.dateStart && getDateKey(props.dateStart) === key ? `${base} selected` : base + } +} + +export const trigEvent = (props: TDatePanelProps, day: TDateCell): void => { + if(day.current !== 0) { + return + } + if (props.type === 'range') { + if (!props.dateStart) { + invokeCallback(props.onSelectStart, day.date) + } else if (!props.dateEnd) { + if(subDateDay(props.dateStart, day.date) !== 0) { + invokeCallback(props.onSelectEnd, day.date) + typeof props.onChange === 'function' && props.onChange(props.type, props) + } + } else { + invokeCallback(props.onReset, day.date) + } + } else { + invokeCallback(props.onSelected, day.date) + typeof props.onChange === 'function' && props.onChange(props.type, props) + } +} + +export const handleDateEnter = (props: TDatePanelProps, day: TDateCell): void => { + if(day.current !== 0) { + return + } + if (props.type === 'range') { + const key = getDateKey(day.date) + if (!props.dateStart || getDateKey(props.dateStart) === key || props.dateEnd) { + return + } + invokeCallback(props.onSelecting, day.date) + } +} \ No newline at end of file diff --git a/devui/datepicker/components/calendar/index.css b/devui/datepicker/components/calendar/index.css new file mode 100644 index 0000000000000000000000000000000000000000..58ad82057e6baf7dd98ea627812e2e1fcb49168b --- /dev/null +++ b/devui/datepicker/components/calendar/index.css @@ -0,0 +1,45 @@ +.calendar-container { + border: 0 solid #aaaaaa; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + user-select: none; + position: relative; +} + +.calendar-panel-time-head { + background-color: #f1f1f1; + text-align: center; + font-size: 16px; + padding: 8px 0; +} + +.calendar-panel-time-select { + display: flex; + flex-direction: row; + width: 150px; + justify-content: space-around; + height: 220px; +} + +.calendar-panel-time-column { + cursor: default; + overflow: auto; + width: 60px; +} + +.calendar-panel-time-column span { + display: block; + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + border-radius: 5px; + overflow: hidden; +} + +.calendar-panel-time-column span.timepicker-selected { + background-color: #00aaff; + color: #ffffff; +} diff --git a/devui/datepicker/components/calendar/index.tsx b/devui/datepicker/components/calendar/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a30c7d7508c3e339da68c7da08f5e749c933cb4e --- /dev/null +++ b/devui/datepicker/components/calendar/index.tsx @@ -0,0 +1,103 @@ +import { defineComponent, onMounted, ref } from 'vue' +import { TProps } from './types' +import CalendarDatePanel from './components/panel' + +import './index.css' + +const TimePicker = defineComponent({ + props: { + time: { type: Date } + }, + setup(props) { + const hour = ref<Element>() + const minute = ref<Element>() + const second = ref<Element>() + const idxes = [0, 0, 0] + onMounted(() => { + if(hour.value) { + hour.value.scrollTop = idxes[0] * 32 + } + if(minute.value) { + minute.value.scrollTop = idxes[1] * 32 + } + if(second.value) { + second.value.scrollTop = idxes[2] * 32 + } + }) + + return () => { + const { time = new Date() } = props || {} + const h = time.getHours(), m = time.getMinutes(), s = time.getSeconds() + return ( + <div class="calendar-panel-time"> + <div class="calendar-panel-time-head"> + <span>{`00:00:00`}</span> + </div> + <div class="calendar-panel-time-select"> + <div ref={hour} class="calendar-panel-time-column">{ + Array(24).fill(0).map((_, i) => { + let className = '' + if (h === i) { + className = 'timepicker-selected' + idxes[0] = i + } + return <span class={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={minute} class="calendar-panel-time-column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (m === i) { + className = 'timepicker-selected' + idxes[1] = i + } + return <span class={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + <div ref={second} class="calendar-panel-time-column">{ + Array(60).fill(0).map((_, i) => { + let className = '' + if (s === i) { + className = 'timepicker-selected' + idxes[2] = i + } + return <span class={className}>{(i + '').padStart(2, '0')}</span> + }) + }</div> + </div> + </div> + ) + } + } +}) + +const Calendar = (props: TProps) => { + const { showTime = false } = props + let { current } = props + if (!(current instanceof Date)) { + current = new Date + } + if (props.type === 'range') { + let { next } = props + if (!(next instanceof Date)) { + next = new Date(current.getFullYear(), current.getMonth() + 1, 1) + } + return ( + <div class="calendar-container"> + <CalendarDatePanel {...props} pos={0} current={current} compare={next} /> + { showTime ? <TimePicker time={current} /> : null } + <CalendarDatePanel {...props} pos={1} current={next} compare={current} /> + { showTime ? <TimePicker time={next} /> : null } + </div> + ) + } else { + return ( + <div class="calendar-container"> + <CalendarDatePanel {...props} pos={0} current={current} /> + { showTime ? <TimePicker time={current} /> : null } + </div> + ) + } +} + +export default Calendar \ No newline at end of file diff --git a/devui/datepicker/components/calendar/types.ts b/devui/datepicker/components/calendar/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..32d3ae4a7b0f6a50e527ae3836990c8e484e0c60 --- /dev/null +++ b/devui/datepicker/components/calendar/types.ts @@ -0,0 +1,52 @@ +export type TDateCell = { date: Date; current: -1 | 0 | 1; } +export type TDatePanelMode = 'month' | 'year' +export type TDatePanelType = 'select' | 'range' +export type TEventCallback = (date: Date, position?: number) => void + +export type TDateConfig = { + type?: TDatePanelType + mode?: TDatePanelMode + current: Date + showTime: boolean +} + +export type TDateSelectingBase = { + dateStart?: Date + dateEnd?: Date + dateHover?: Date +} + +export type TDateToolbarEventProps = { + onPreviousYear?: TEventCallback + onPreviousMonth?: TEventCallback + onNextMonth?: TEventCallback + onNextYear?: TEventCallback +} + +export type TDateToolbarDataProps = TDateConfig & { + pos?: number + compare?: Date +} + +export type TDateToolbarProps = TDateToolbarDataProps & TDateToolbarEventProps + +export type TDatePanelEventProps = TDateToolbarEventProps & { + onSelected?: TEventCallback + onReset?: TEventCallback + onSelectStart?: TEventCallback + onSelectEnd?: TEventCallback + onSelecting?: TEventCallback + onChange?: (type: TDatePanelType, config: TDateSelectingBase) => void +} + +export type TDatePanelDataProps = TDateToolbarDataProps & TDateSelectingBase + +export type TDatePanelProps = TDatePanelDataProps & TDatePanelEventProps + + +export type TProps = ({ + type: 'select' +} | { + type: 'range' + next: Date +}) & TDateConfig & TDateSelectingBase & TDatePanelEventProps \ No newline at end of file diff --git a/devui/datepicker/components/calendar/utils.ts b/devui/datepicker/components/calendar/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..280fae286b970793319d491c5014d04798cdf997 --- /dev/null +++ b/devui/datepicker/components/calendar/utils.ts @@ -0,0 +1,116 @@ +import { TDateCell } from './types' + +const getHumanDate = (d: Date) => { + const year = d.getFullYear() + const month = d.getMonth() + 1 + const date = d.getDate() + const day = d.getDay() + const hour = d.getHours() + const minute = d.getMinutes() + const second = d.getSeconds() + const ms = d.getMilliseconds() + + return { + year, y: year, month, M: month, date, d: date, day, + hour, H: hour, h: hour, + minute, m: minute, + second, s: second, + ms, + } +} + +const getMonthDays = (year: number, month: number) => { + const first = new Date(year, month - 1, 1) + const last = new Date(year, month, 0) + const dates: TDateCell[] = [] + + let day = first.getDay() + while (day > 0) { + day -= 1 + dates.push({ date: new Date(year, month - 1, -day), current: -1 }) + } + + day = last.getDate() - first.getDate() + for (let i = 0; i <= day; i++) { + const date = new Date(first) + date.setDate(i + 1) + dates.push({ date, current: 0 }) + } + + day = last.getDay() + let tail: Date = last + for (let i = day; i < 6; i++) { + tail = new Date(year, month, i - day + 1) + dates.push({ date: tail, current: 1 }) + } + if(dates.length < 42) { + day = tail.getDate() + for (let i = 1; i <= 7; i++) { + tail = new Date(year, month, day + i) + dates.push({ date: tail, current: 1 }) + } + } + return dates +} + +export const getMonthWeeklyDays = (date: any = new Date()) => { + if (!(date instanceof Date)) { + date = new Date() + } + const { year, month } = getHumanDate(date) + const days = getMonthDays(year, month) + const dayRows: TDateCell[][] = [] + while (days.length > 0) { + dayRows.push(days.splice(0, 7)) + } + return dayRows +} + +export const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六'] + +export const invokeCallback = (cb: any, ...args: any[]) => { + typeof cb === 'function' && cb(...args) +} + +/** + * a - b 的月数 + */ +export const subDateMonth = (a: Date, b: Date) => { + const am = a.getFullYear() * 12 + a.getMonth() + const bm = b.getFullYear() * 12 + b.getMonth() + return am - bm +} + +const ONE_DAY = 1000 * 60 * 60 * 24 + +/** + * a - b 的天数 + * @param a + * @param b + * @returns + */ +export const subDateDay = (a: Date, b: Date) => { + const ad = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime() + const bd = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime() + return (ad - bd) / ONE_DAY +} + +/** +* 比较日期单位 +* @param small 相对早的日期 +* @param big 相对晚的日期 +* @param mode 比较单位 +* @param min 不能小于这个值 +* @returns +*/ +export const compareDate = (small: Date | undefined, big: Date | undefined, mode: 'year' | 'month', min: number) => { + if (!small || !big) { + return true + } + if (mode === 'year') { + return big.getFullYear() - small.getFullYear() > min + } else { + return subDateMonth(big, small) > min + } +} + diff --git a/devui/datepicker/components/input/index.css b/devui/datepicker/components/input/index.css new file mode 100644 index 0000000000000000000000000000000000000000..0707f8c3e28dca992e919e4a8deb6e87fca5a0ff --- /dev/null +++ b/devui/datepicker/components/input/index.css @@ -0,0 +1,28 @@ +.datapicker-input-container { + position: relative; +} + +.datapicker-input-border { + border: 1px solid #526ecc; + padding: 0 5px; + border-radius: 4px; + box-sizing: border-box; + height: 28px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + +.datapicker-input { + border: none; + outline: none; + position: relative; + padding: 0; + line-height: 16px; + height: 16px; + font-size: 12px; + flex-shrink: 1; + flex-grow: 1; + width: 100%; +} diff --git a/devui/datepicker/components/input/index.tsx b/devui/datepicker/components/input/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6904d37e46b030e70af7761888be3d210c3d18eb --- /dev/null +++ b/devui/datepicker/components/input/index.tsx @@ -0,0 +1,33 @@ +import { ref } from 'vue' +import Icon from '../../../icon' +import './index.css' + +type TProps = { + width?: number + value?: string + placeholder?: string + onActive?: (el: Element) => void +} + +const DatepickerInput = (props: TProps) => { + const { width = 160, placeholder = '', value = '', onActive } = props + const container = ref<Element>() + const handleClick = () => { + if(container.value && typeof onActive === 'function') { + onActive(container.value) + } + } + return ( + <div + ref={container} + className="datapicker-input-border" + style={{ width: `${width}px` }} + onClick={handleClick} + > + <input className="datapicker-input" type="text" value={value} placeholder={placeholder} /> + <Icon name="calendar" size="16px" /> + </div> + ) +} + +export default DatepickerInput \ No newline at end of file diff --git a/devui/datepicker/components/pop-panel/index.css b/devui/datepicker/components/pop-panel/index.css new file mode 100644 index 0000000000000000000000000000000000000000..ba794c3ea364602530e192a12fdc6d934ef54ad0 --- /dev/null +++ b/devui/datepicker/components/pop-panel/index.css @@ -0,0 +1,6 @@ +.datapicker-pop-panel { + display: block; + position: absolute; + background-color: #ffffff; + z-index: 99; +} diff --git a/devui/datepicker/components/pop-panel/index.tsx b/devui/datepicker/components/pop-panel/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..022659dac00940371bd35bfe56419987b820e8bc --- /dev/null +++ b/devui/datepicker/components/pop-panel/index.tsx @@ -0,0 +1,41 @@ +import { defineComponent, ref, renderSlot, SetupContext } from 'vue' + +import './index.css' + +type TProps = { + show?: boolean + background?: string + xPosition?: 'left' | 'right' + yPosition?: 'top' | 'bottom' + xOffset?: number + yOffset?: number + children?: any +} + +const PopPanel = (props: TProps, ctx: SetupContext) => { + const container = ref<Element>() + if (!props.show) { + return null + } + const { + xPosition = 'left', yPosition = 'top', + xOffset = 0, yOffset = 0, + } = props + + const left = xPosition === 'left' ? `${xOffset}px` : 'auto' + const right = xPosition === 'right' ? `${xOffset}px` : 'auto' + const top = yPosition === 'top' ? `${yOffset}px` : 'auto' + const bottom = yPosition === 'bottom' ? `${yOffset}px` : 'auto' + + const children: any = renderSlot(ctx.slots, 'default') + + return ( + <div + ref={container} + className="datapicker-pop-panel" + style={{ left, right, top, bottom }} + >{children}</div> + ) +} + +export default PopPanel diff --git a/devui/datepicker/datepicker.css b/devui/datepicker/datepicker.css new file mode 100644 index 0000000000000000000000000000000000000000..4eb33598f9ffa579a550796e6624677f7cf7966b --- /dev/null +++ b/devui/datepicker/datepicker.css @@ -0,0 +1,19 @@ +.datepicker-global-viewport { + position: fixed; + left: 0; + top: 0; + width: 0; + height: 0; + background-color: rgba(0, 0, 0, 0.2); + z-index: 99; + overflow: visible; +} + +.datepicker-container { + margin: 0; + padding: 0; + position: relative; + display: inline-block; + border: 1px solid #cccccc; + background-color: #ffffff; +} diff --git a/devui/datepicker/datepicker.tsx b/devui/datepicker/datepicker.tsx index 1b01bbc08caf0c3220a958347bb9b49c924010e6..38a9edb85c9a21b59ee89d1cca315ab8e6c03463 100644 --- a/devui/datepicker/datepicker.tsx +++ b/devui/datepicker/datepicker.tsx @@ -1,12 +1,256 @@ -import { defineComponent } from 'vue' +import { defineComponent, reactive, onMounted, onUnmounted, ref } from 'vue' +import { + EventManager, + formatDate, formatRange, isIn, + traceNode, invokeFunction, +} from './utils' +import Calendar from './components/calendar' +import './datepicker.css' + +type TState = { + range?: boolean + current?: Date + next?: Date + start?: Date + end?: Date + hover?: Date + show?: boolean + input?: string + st?: boolean +} + +/** + * Calendar 面板年月切换逻辑 + * @param state + * @param index + * @param pos + * @param date + */ +const handleCalendarSwitchState = (state: TState, index: number, pos: number, date: Date) => { + switch(index) { + case 0: // previous year + const preYear = new Date(date) + preYear.setFullYear(preYear.getFullYear() - 1) + pos === 0 ? (state.current = preYear) : (state.next = preYear) + break + case 1: // previous month + const preMonth = new Date(date) + preMonth.setMonth(preMonth.getMonth() - 1) + pos === 0 ? (state.current = preMonth) : (state.next = preMonth) + break + case 2: // next month + const nextMonth = new Date(date) + nextMonth.setMonth(nextMonth.getMonth() + 1) + pos === 0 ? (state.current = nextMonth) : (state.next = nextMonth) + break + case 3: // next year + const nextYear = new Date(date) + nextYear.setFullYear(nextYear.getFullYear() + 1) + pos === 0 ? (state.current = nextYear) : (state.next = nextYear) + break + } +} + +/** + * 格式化日期 + * @param state + * @param props + * @returns + */ +const formatOutputValue = (state: TState, props: any) => { + const { format = 'y/MM/dd', range, rangeSpliter = '-' } = props || {} + if(range) { + return formatRange(format, + state.start, + state.end, + rangeSpliter + ) + } else { + return formatDate(format, state.start) + } +} + +const getPlaceholder = (props: any) => { + if(!props) return '' + const format = props.format || `y/MM/dd` + const sp = props.rangeSpliter || '-' + return props.range ? `${format} ${sp} ${format}` : format +} + +/** + * 输出日期选择结果 + * @param id + * @param output + */ +const handleDomOutput = (id: string | undefined, output: string) => { + if(id && typeof id === 'string') { + const el = document.querySelector(id) + if(el instanceof HTMLInputElement) { + el.value = output + } + } +} export default defineComponent({ - name: 'd-datepicker', + name: 'DDatepicker', props: { + selectedDateChange: { type: Function }, + autoClose: { type: Boolean, default: false }, + range: { type: Boolean, default: false }, + showTime: { type: Boolean, default: false }, + format: { type: String, default: 'y/MM/dd' }, + rangeSpliter: { type: String, default: '-' }, + attachInputDom: { type: String }, }, setup(props, ctx) { + + const container = ref<Element>() + const evtman = new EventManager() + + const current = new Date() + + const state = reactive<TState>({ + range: !!props.range, + current, + next: new Date(current.getFullYear(), current.getMonth() + 1, 1), + show: false, + input: props.attachInputDom, + st: true + }) + + const pos = reactive<{ + x: string + y: string + }>({ + x: '0', + y: '0', + }) + + /** + * 获取绑定节点 + * @returns + */ + const getAttachInputDom = () => { + const { attachInputDom } = props || {} + if(!attachInputDom || typeof attachInputDom !== 'string') { + return null + } + const el = document.querySelector(attachInputDom) + if(!el) { + return null + } + state.st = false + return el + } + + /** + * 绑定弹出层场景,计算弹出层位置。 + * @returns + */ + const handlePosition = () => { + if(!state.show) { + pos.x = `-100%` + pos.y = `-100%` + return + } + const el = getAttachInputDom() + if(!el) { + return + } + const { left, top, width, height } = el.getBoundingClientRect() + const { width: _width, height: _height } = container.value.getBoundingClientRect() + const bottom = window.innerHeight - top - height + pos.x = `${left}px` + if(bottom > top) { + pos.y = `${top + height}px` + } else { + pos.y = `${top - _height}px` + } + + } + + onMounted(() => { + // 获取绑定节点(默认input) + const el = getAttachInputDom() + // 绑定节点不存在,作为普通组件展示。 + if(!el) { + // 显示组件 + state.show = true + return + } else { + // 作为弹出层,先隐藏 + state.show = false + } + + // 判断节点原生DOM类型 + // 对input节点的值处理 + if(el instanceof HTMLInputElement) { + // 设置水印文字 + el.placeholder = getPlaceholder(props) + } + + // 绑定节点click事件处理弹出层显示 + evtman.append(el, 'click', () => !state.show && (state.show = true)) + // document层处理`点击其他区域隐藏` + evtman.append(document, 'click', (e: MouseEvent) => { + if(!state.show || e.target === el || isIn(e.target as Node, container.value)) { + return + } + state.show = false + }) + // 对绑定节点做scroll跟踪,并绑定跟踪事件 + traceNode(el).forEach(node => { + evtman.append(node, 'scroll', handlePosition) + }) + }) + + onUnmounted(() => { + evtman.dispose() + }) + return () => { - return <div>devui-datepicker</div> + handlePosition() + return ( + <div class={state.st ? `` : `datepicker-global-viewport`}> + <div + ref={container} + class="datepicker-container" + style={{ + transform: state.st ? '' : `translateX(${pos.x}) translateY(${pos.y})` + }} + > + <Calendar + type={props.range ? 'range' : 'select'} + showTime={props.showTime} + current={state.current} + next={state.next} + dateStart={state.start} + dateEnd={state.end} + dateHover={state.hover} + onReset={(date: Date) => { + state.end = state.hover = undefined + state.start = date + }} + onChange={() => { + const output = formatOutputValue(state, props) + handleDomOutput(state.input, output) + invokeFunction(props.selectedDateChange, output) + if(props.autoClose) { + state.show = false + } + }} + onSelected={(date: Date) => state.start = date} + onSelectStart={(date: Date) => state.start = date} + onSelectEnd={(date: Date) => state.end = date} + onSelecting={(date: Date) => state.hover = date} + onPreviousYear={(date: Date, pos: number) => handleCalendarSwitchState(state, 0, pos, date)} + onPreviousMonth={(date: Date, pos: number) => handleCalendarSwitchState(state, 1, pos, date)} + onNextMonth={(date: Date, pos: number) => handleCalendarSwitchState(state, 2, pos, date)} + onNextYear={(date: Date, pos: number) => handleCalendarSwitchState(state, 3, pos, date)} + /> + </div> + </div> + ) } } }) \ No newline at end of file diff --git a/devui/datepicker/demo/datepicker-demo.tsx b/devui/datepicker/demo/datepicker-demo.tsx index 995d9cdaafb96b1162276f7bad158fe70ead3d15..431cfb7c0d6da7c9051c1c8caf3cb9dcbfdcd092 100644 --- a/devui/datepicker/demo/datepicker-demo.tsx +++ b/devui/datepicker/demo/datepicker-demo.tsx @@ -1,12 +1,109 @@ import { defineComponent } from 'vue' +import DatePicker from '../datepicker' export default defineComponent({ - name: 'd-datepicker-demo', + name: 'DDatepickerDemo', props: { }, setup(props, ctx) { return () => { - return <div>devui-datepicker-demo</div> + return ( + <div> + <DatePicker /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /><br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /><br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <div style="margin-left: 200px;"> + <DatePicker /> + </div> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /><br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /><br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <DatePicker /> + </div> + ) } } }) \ No newline at end of file diff --git a/devui/datepicker/doc-demo/demo1.tsx b/devui/datepicker/doc-demo/demo1.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1cd5a635454bf0d7c6d17578752bef025f1134fa --- /dev/null +++ b/devui/datepicker/doc-demo/demo1.tsx @@ -0,0 +1,30 @@ +import { defineComponent, reactive } from 'vue' +import DatePicker from '..' + +export default defineComponent({ + name: 'DDatepickerDemo1', + setup() { + const state = reactive<{ + range: boolean + showTime: boolean + }>({ + range: false, + showTime: false, + }) + return () => { + return (<div> + <div style="line-height: 32px;font-size:13px;user-select:none"> + <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <span>区域选择</span> + <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.range = !state.range} checked={state.range} /> + </label> + <label style="margin-right:5px;border:1px solid #aaa;padding:5px 10px;border-radius:5px;cursor:pointer;"> + <span>显示时间</span> + <input style="margin:0px;transform:translateX(3px) translateY(2px);" type="checkbox" onChange={() => state.showTime = !state.showTime} checked={state.showTime} /> + </label> + </div> + <DatePicker range={state.range} showTime={state.showTime} /> + </div>) + } + } +}) diff --git a/devui/datepicker/index.ts b/devui/datepicker/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..17134f3efdb22cc8d11596d35b8ec09b23dbc6af --- /dev/null +++ b/devui/datepicker/index.ts @@ -0,0 +1,10 @@ +import { App } from 'vue' +import DatePicker from './datepicker' + +DatePicker.install = function(Vue: App) { + Vue.component(DatePicker.name, DatePicker) +}; + +DatePicker.version = '0.0.1' + +export default DatePicker diff --git a/devui/datepicker/utils.ts b/devui/datepicker/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..aebc7b074e9d2edbac4b286e4127ce5ea03e09f9 --- /dev/null +++ b/devui/datepicker/utils.ts @@ -0,0 +1,103 @@ +const getDateTime = (d: Date) => { + const year = d.getFullYear() + const month = d.getMonth() + 1 + const date = d.getDate() + const day = d.getDay() + const hour = d.getHours() + const minute = d.getMinutes() + const second = d.getSeconds() + const ms = d.getMilliseconds() + return [year, month, date, day, hour, minute, second, ms] +} + +const fixStart = (n: number, m: string, max = 2, ch = '0') => { + return (n + '').padStart(Math.min(m.length, max), ch) +} + +/** + * - y: year yy 取后2位,其他情况取4位 + * - M: month 最多取2位补0 + * @param fmt + * @param d + */ +export const formatDate = (fmt: string, d: Date) => { + const usage = getDateTime(d) + let res = fmt + res = res.replace(/y+/g, m => { + const year = usage[0] + '' + if (m.length === 2) { + return year.substring(2) + } + return year + }) + res = res.replace(/M+/g, m => fixStart(usage[1], m)) + res = res.replace(/d+/g, m => fixStart(usage[2], m)) + res = res.replace(/h+/g, m => fixStart(usage[4], m)) + res = res.replace(/m+/g, m => fixStart(usage[5], m)) + res = res.replace(/s+/g, m => fixStart(usage[6], m)) + return res +} + +export const formatRange = (fmt: string, a: Date, b: Date, conn = '-') => { + const ab = [a, b] + if(a.getTime() > b.getTime()) { + ab.reverse() + } + return `${formatDate(fmt, ab[0])} ${conn} ${formatDate(fmt, ab[1])}` +} + +/** + * 判断节点a是否在节点b中 + * @param a + * @param b + * @returns + */ +export const isIn = (a: Node | null, b: Node | null) => { + if (!b) { + return false + } + while (a) { + if (a === b) { + return true + } + a = a.parentNode + } + return false +} + +type EventItem = { el: Node | Window; cb: (...args: any[]) => any; name: string; capture: boolean; } +export class EventManager { + private readonly items: EventItem[] + constructor() { + this.items = [] + } + + append(el: Node | Window, name: string, cb: (...args: any[]) => any, capture = false) { + el.addEventListener(name, cb, capture) + this.items.push({ el, name, cb, capture }) + } + + dispose() { + this.items.splice(0, this.items.length).forEach(({ el, name, cb, capture }) => { + el.removeEventListener(name, cb, capture) + }) + } +} + +export const traceNode = (el: Node) => { + const els: Node[] = [], name = 'scroll' + while (el.parentNode) { + els.push(el.parentNode) + el = el.parentNode + } + return els +} + +/** + * 函数安全调用 + */ +export const invokeFunction = (fn: any, ...args: any[]) => { + if (typeof fn === 'function') { + fn(...args) + } +} \ No newline at end of file diff --git a/devui/loading/__tests__/loading.spec.ts b/devui/loading/__tests__/loading.spec.ts index be8b2b5fb731299bc7b61c1752d13d77916ae039..bc4ad4faf14cd2746d06d4ca9040c53affc2cc80 100644 --- a/devui/loading/__tests__/loading.spec.ts +++ b/devui/loading/__tests__/loading.spec.ts @@ -57,8 +57,9 @@ describe('Loading as directive', () => { const loadingPType = wrapper.find('#testLoading') expect(loadingPType).toBeTruthy() - // @ts-ignore - const targetEle = loadingPType.wrapperElement.instance.vnode.el + // @_ts-ignore + // 不支持`ts-ignore`,强行修改确保eslint通过。@mrundef-210810 + const targetEle = (loadingPType as any).wrapperElement.instance.vnode.el expect(targetEle.parentNode.style.position).toEqual('absolute') }) @@ -175,11 +176,11 @@ describe('Loading as directive', () => { </div> `, setup() { - let promises: any = shallowReactive({ + const promises: any = shallowReactive({ value: [] }) const fetchMutiplePromise = () => { - let list = [] + const list = [] for (let i = 0; i < 3; i++) { list.push(new Promise((res: any) => { res(true) @@ -213,13 +214,13 @@ describe('Loading as Service', () => { const loading = LoadingService.open() await nextTick() - let ele = document.querySelector('.devui-loading-contanier') + const ele = document.querySelector('.devui-loading-contanier') expect(ele).toBeTruthy() expect(ele.parentNode == document.body).toBe(true) loading.loadingInstance.close() await nextTick() - let ele2 = document.querySelector('.devui-loading-contanier') + const ele2 = document.querySelector('.devui-loading-contanier') expect(ele2).toBe(null) }) @@ -232,7 +233,7 @@ describe('Loading as Service', () => { }) await nextTick() - let ele = document.querySelector('.devui-loading-contanier') + const ele = document.querySelector('.devui-loading-contanier') expect(ele).toBeTruthy() expect(ele.parentNode === div).toBe(true) @@ -245,7 +246,7 @@ describe('Loading as Service', () => { }) await nextTick() - let ele = document.querySelector('.devui-loading-contanier') + const ele = document.querySelector('.devui-loading-contanier') expect(ele).toBeTruthy() expect(ele.textContent).toBe('正在加载中...') @@ -263,14 +264,16 @@ describe('Loading as Service', () => { }) await nextTick() - let ele = document.querySelector('.devui-loading-contanier') + const ele = document.querySelector('.devui-loading-contanier') expect(ele).toBeTruthy() - // @ts-ignore - expect(ele.parentNode.style.position).toBe('absolute') - - let loadingEle = ele.querySelector('.devui-loading-area') - // @ts-ignore - const style = loadingEle.style + // @_ts-ignore + // 不支持`ts-ignore`,强行修改确保eslint通过。@mrundef-210810 + expect((ele.parentNode as any).style.position).toBe('absolute') + + const loadingEle = ele.querySelector('.devui-loading-area') + // @_ts-ignore + // 不支持`ts-ignore`,强行修改确保eslint通过。@mrundef-210810 + const style = (loadingEle as any).style expect(style.top).toBe('40%') expect(style.left).toBe('60%') expect(style.zIndex).toBe('1000') diff --git a/devui/loading/src/directive.ts b/devui/loading/src/directive.ts index a99daebe5bd1907060acae378e30d4124c20ea88..5a2b02ad5139543c4d5135f00f4c80dddc096604 100644 --- a/devui/loading/src/directive.ts +++ b/devui/loading/src/directive.ts @@ -66,8 +66,11 @@ const toggleLoading = (el: TargetHTMLElement, binding: BindingType) => { cacheInstance.add(el) if (vals) { - Promise.all(vals).then((res: Array<boolean>) => { - }).catch((err: any) => { + Promise.all(vals) + // eslint不允许空的then执行体 @mrundef-210810 + // .then((res: Array<boolean>) => { + // }) + .catch((err: any) => { console.error(new Error('Promise handling errors'), err) }).finally(() => { unmount(el) @@ -93,7 +96,7 @@ const handleProps = (el: TargetHTMLElement, vprops: LoadingProps) => { ...vprops } - let loadingTemplateRef = props.loadingTemplateRef + const loadingTemplateRef = props.loadingTemplateRef const loadingInstance = createComponent( loadingConstructor, @@ -128,8 +131,8 @@ const loadingDirective = { toggleLoading(el, binding) }, - - unmounted: function () { } + // eslint不允许控的unmounted执行体 + // unmounted: function () { } } export default loadingDirective diff --git a/devui/loading/src/service.ts b/devui/loading/src/service.ts index 2c8642de85c211ba0d2dafe2d2a8b111c9d19e01..2794dc1f6fbc9690a27eaf7689d058b750e4b4cf 100644 --- a/devui/loading/src/service.ts +++ b/devui/loading/src/service.ts @@ -40,9 +40,12 @@ const loading = { const close = instance.proxy.close instance.loadingInstance = instance.proxy - instance.loadingInstance.close = () => { + instance.loadingInstance.close = (...args: any[]) => { cacheTarget.delete(parent) - close.apply(null, arguments) + // 1. 箭头函数内部并没有内置arguments对象 @mrundef-210810 + // 2. 如果没有上下文要求`apply(null)`,不必使用apply/call + // close.apply(null, arguments) + close(...args) } return instance diff --git a/package.json b/package.json index 087fcf98655af851ec901c27dafb346c72448bd0..809ff37460105233bad1782d92b6be693fc3b90f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "devui-cli": "./devui-cli/index.js" }, "scripts": { + "predev": "npm run generate:devui", "dev": "vitepress dev sites", "build": "vitepress build sites", "serve": "vitepress serve sites", diff --git a/scripts/generate-devui.js b/scripts/generate-devui.js index 34976e1a6281aae361cf4a6f33933aa84b0b37ff..b180ce9005fabe8983f8e65c6373eccd6b724b25 100644 --- a/scripts/generate-devui.js +++ b/scripts/generate-devui.js @@ -9,6 +9,8 @@ console.log('config:', config); let importStr = `import { App } from 'vue';\n\n`; const components = []; +const getDocDemos = require('./get-doc-demos') + config['/'].forEach(({ text: ctext, children }) => { if (ctext !== '快速开始') { importStr += `// ${ctext}\n`; @@ -20,6 +22,11 @@ config['/'].forEach(({ text: ctext, children }) => { const filename = linkItem[1]; importStr += `import ${name} from './${filename}';\n`; components.push(name); + + getDocDemos(filename).forEach(({ name, from }) => { + importStr += `import ${name} from './${from}';\n`; + components.push(name); + }) }) importStr += `\n`; } diff --git a/scripts/get-doc-demos.js b/scripts/get-doc-demos.js new file mode 100644 index 0000000000000000000000000000000000000000..deac9748beb49b4812c25fc5a7e53f949ba112c4 --- /dev/null +++ b/scripts/get-doc-demos.js @@ -0,0 +1,44 @@ +const path = require('path'); +const fs = require('fs'); + +const UI_BASE_DIR = path.resolve(__dirname, '../devui') + +/** + * 读取UI组件库中的`doc-demo`目录,收集供文档使用的`demo`组件。 + * - mrundef - 210811 + * @param {string} compName 组件名称 + * @param {string} docDemoDir demo组件存放目录名称,默认`doc-demo` + * @returns + */ +const getDocDemos = (compName, docDemoDir = 'doc-demo') => { + const res = [] + /** 组装`doc-demo`路径 */ + const docDemoPath = path.join(UI_BASE_DIR, compName, docDemoDir) + /** 路径存在 且是目录 */ + if (fs.existsSync(docDemoPath) && fs.statSync(docDemoPath).isDirectory()) { + /** 获取有效组件文件 */ + const names = fs.readdirSync(docDemoPath, 'utf-8') + // 过滤隐藏文件名 + .filter(n => !/^\./.test(n)) + // 仅保留文件 + .filter(n => fs.statSync(path.join(docDemoPath, n)).isFile()) + // 过滤文件类型 js/jsx/ts/tsx/vue + .filter(n => /\.(([jt]sx?|vue)?)$/.test(n)) + names.forEach(name => { + /** 去掉扩展名 */ + const n = name.replace(/\.[^\.\\\/]+$/, '') + /** 组装引入命名名称 */ + const k = `${compName}_${n}` + /** 确保没有重复引用 */ + if (!res.some(({ name }) => name === k)) { + res.push({ + from: `${compName}/doc-demo/${n}`, + name: k + }) + } + }) + } + return res +} + +module.exports = getDocDemos \ No newline at end of file diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index 2d639b4ff14139647fa4aa011514bbdb89df9a46..e468cd7491216b203190fd915478e81f9d8eea19 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -25,6 +25,7 @@ const sidebar = { text: '数据录入', children: [ { text: 'Checkbox 复选框', link: '/components/checkbox/' }, + { text: 'DatePicker 日期选择器', link: '/components/datepicker/' }, { text: 'Radio 单选框', link: '/components/radio/' }, { text: 'Select 下拉选择框', link: '/components/select/' }, { text: 'Switch 开关', link: '/components/switch/' }, diff --git a/sites/components/datepicker/index.md b/sites/components/datepicker/index.md new file mode 100644 index 0000000000000000000000000000000000000000..4f462751eef2d6560909ab6c311efa93bd0ad763 --- /dev/null +++ b/sites/components/datepicker/index.md @@ -0,0 +1,124 @@ +# DatePicker 日期选择器 + +日期、时间可视化输入。 + +### 作为UI组件 + +```jsx +// 默认 range=false +<d-datepicker range={state.range} showTime={state.showTime} /> +``` + +<d-datepicker-demo-1 /> + +### 绑定原生`<input>` + +暂定通过`querySelector`查找节点,绑定真实`dom`节点。此方案待定。 + +```jsx +// 选取完成后保留弹层 +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> +<d-datepicker attach-input-dom="#datepicker-input" /> +``` + +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input" /> +<d-datepicker attach-input-dom="#datepicker-input" /> + +```jsx +// auto-close=true 选取完成后自动关闭 +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> +<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> +``` + +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-autoclose" /> +<d-datepicker auto-close attach-input-dom="#datepicker-input-autoclose" /> + +### 区域选择 + +```jsx +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> +<d-datepicker range attach-input-dom="#datepicker-input-range" /> +``` + +<input style="width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-range" /> +<d-datepicker range attach-input-dom="#datepicker-input-range" /> + + +### Scroll位置跟踪 + +在对宿主`<input>`绑定的过程中,对宿主的父级节点做了`scroll`追踪;当任何一级发生`scroll`时,都会更新弹出层位置,确保能让弹出层与`<input>`正确贴合。 + +这个做法是根据`ng`的组件效果实现的。 + +TODO: 跟踪节流。 + +```jsx +<div style="border:1px solid #aaa;height:300px;overflow:auto;"> + <br /> + // ... + <i>占行</i> + // ... + <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> + <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> + // ... + <br /> + <i>占行</i> + // ... +</div> +``` + +<div style="border:1px solid #aaa;height:500px;overflow:auto;"> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <input style="margin-left:100px;width:200px;padding:5px;font-size:16px;border-radius:5px;" id="datepicker-input-tracing" /> + <d-datepicker auto-close range attach-input-dom="#datepicker-input-tracing" /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <i>占行</i> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <br /> + <i>占行</i> +</div> + + diff --git a/yarn.lock b/yarn.lock index 95a75658aeb3c155f5d5e256be4ace1ff82357c8..2e22957cdff1b5a09aac2426ec8de02eefaa98ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1202,8 +1202,8 @@ "@devui-design/icons@^1.3.0": version "1.3.0" - resolved "https://registry.nlark.com/@devui-design/icons/download/@devui-design/icons-1.3.0.tgz#5a3006a31ee4f62e3f9837b68c031898ff148b88" - integrity sha1-WjAGox7k9i4/mDe2jAMYmP8Ui4g= + resolved "https://registry.npmjs.org/@devui-design/icons/-/icons-1.3.0.tgz#5a3006a31ee4f62e3f9837b68c031898ff148b88" + integrity sha512-eg9PcDXYn1BaUCo6qP7dszFJ4uRycxASLdKzpz27AW34HbHx4kFce36Fhfr2niRjGKYllcMgYe7hgP5KYVOZ8Q== "@docsearch/css@^1.0.0-alpha.28": version "1.0.0-alpha.28"