diff --git a/src/components/base/custome_element_l10n.ts b/src/components/base/custome_element_l10n.ts new file mode 100644 index 0000000..ba666ab --- /dev/null +++ b/src/components/base/custome_element_l10n.ts @@ -0,0 +1,179 @@ +import {StarBaseElement} from './star-base-element' + +interface PropertiesParser { + patterns: {[type: string]: RegExp} + entryIds: object + init: () => void + parse: (ctx: any, source: string) => any[] + parseEntity: (id: string, value: string, ast: string[]) => void + setEntityValue: ( + id: string, + attr: string, + key: string, + rawValue: string, + ast: any[] + ) => any + parseString: (str: string) => any[] + unescapeString: (str: string) => string + parseIndex: (str: string) => (string | {t: string; v: string})[] +} + +class L10nHelper { + /* 开启了国际化功能的组件 */ + updateArray: Set = new Set() + // TODO: fluent.js 替换完后更改此处 + l10n: any + /* 国际化资源转化器 */ + PropertiesParser?: PropertiesParser + suspending: Function[] = [] + constructor() { + this.init() + } + + init = (): void => { + // @ts-ignore + const l10n = navigator.mozL10n + if (!l10n) { + return document.addEventListener('readystatechange', this.init, { + once: true, + }) + } + this.l10n = l10n + + /* 变更语言时, 刷新组件 */ + window.addEventListener('languagechange', () => { + this.updateArray.forEach((element) => element.requestUpdate()) + }) + + if (this.suspending.length) { + this.suspending.forEach((fun) => fun()) + } + + this.loadAllLocales() + } + + /* 空闲时加载所有国际化资源, 防止语言切换时闪烁 */ + loadAllLocales = () => { + requestIdleCallback(() => { + const availableLocales: string[] = this.l10n.ctx.availableLocales + const shouldRequest: string[] = [] + availableLocales.forEach((lang) => { + const locale = this.l10n.ctx.getLocale(lang) + if (!locale.isReady) { + shouldRequest.push(lang) + } + }) + + shouldRequest.length && this.l10n.ctx.requestLocales(...shouldRequest) + }) + } + + observe = (el: StarBaseElement) => { + this.updateArray.add(el) + } + + unobserve = (el: StarBaseElement) => { + this.updateArray.delete(el) + } + + /* 添加国际化资源 */ + addLocaleSrc = (json: {[lang: string]: {[l10nId: string]: string}}): void => { + if (!this.l10n || !this.l10n.ctx.isReady) { + this.suspending.push(this.addLocaleSrc.bind(this, json)) + return + } + this.initParser().then(() => { + for (const language in json) { + const context = json[language] + const ast = this.parse(context) + const locale = this.l10n.ctx.getLocale(language) + locale.addAST(ast) + } + }) + } + + /* 初始化转化器 */ + initParser = () => { + if (this.l10n.ctx.isReady && this.PropertiesParser) { + return Promise.resolve() + } + + return new Promise((res) => { + this.l10n.once(() => { + this.PropertiesParser = this.l10n._getInternalAPI() + .PropertiesParser as PropertiesParser + this.PropertiesParser.init() + res(void 0) + }) + }) + } + + parse = (context: {[l10nId: string]: string}) => { + let str = '' + for (const key in context) { + const value = context[key] + str += `${key}=${value}\n` + } + + return this.PropertiesParser!.parse(null, str) + } + + get = (id: string, ctxdata: any) => { + let result = '' + + if (this.l10n) { + if (this.l10n.ctx.isReady) { + // 如果L10n已经准备好, 则获取对应国际化结果 + if (this.l10n.ctx.getLocale(navigator.language).isReady) { + result = this.l10n.get(id, ctxdata) + if (!result) { + console.warn( + 'l10n get nothing by id:', + id, + '; and ctxdata:', + ctxdata + ) + } + } else { + // 相应的国际化资源未准备完成, 待完成后重新刷新 + this.openLocaleListener() + } + } else { + // L10n还没有准备好, 等待L10n准备完毕后, 刷新小组件 + this.openL10nReadyListener() + } + } else { + console.warn('l10n is not exist!') + } + + return result + } + + /* 等待国际化资源加载, 添加加载成功的事件监听 */ + _localeListening: boolean = false + openLocaleListener = () => { + if (!this._localeListening) { + this._localeListening = true + const cb = () => { + this.updateArray.forEach((el) => el.requestUpdate()) + this.l10n.ctx.removeEventListener('request-locales-done', cb) + this._localeListening = false + } + this.l10n.ctx.addEventListener('request-locales-done', cb) + } + } + + /* 等待L10n准备完毕, 添加准备完毕的事件监听 */ + _readyListening: boolean = false + openL10nReadyListener = () => { + if (!this._readyListening) { + this._readyListening = true + this.l10n.once(() => { + this.updateArray.forEach((el) => el.requestUpdate()) + this._readyListening = false + }) + } + } +} + +export const l10nHelper = new L10nHelper() diff --git a/src/components/base/star-base-element.ts b/src/components/base/star-base-element.ts index 4bd62c5..adbd215 100644 --- a/src/components/base/star-base-element.ts +++ b/src/components/base/star-base-element.ts @@ -5,6 +5,7 @@ import GestureDetector, { } from '../../lib/gesture/gesture-detector' import {autoPxStyle} from './auto-px-style' import {globalStyles} from './global-style' +import {l10nHelper} from './custome_element_l10n' declare global { var loadStarMixin: boolean @@ -113,6 +114,16 @@ export type StarElementEventMap = HTMLElementEventMap & GestureEvents export class StarBaseElement extends StarMixin(LitElement) { gestureDetector!: GestureDetector + disconnectedCallback(): void { + super.disconnectedCallback() + this._l10n && l10nHelper.observe(this) + } + + connectedCallback(): void { + super.connectedCallback() + this._l10n && l10nHelper.unobserve(this) + } + /** * 启动手势监听框架 */ @@ -126,6 +137,35 @@ export class StarBaseElement extends StarMixin(LitElement) { GestureDetector.disembedded(this) } + /** + * 动态添加国际化资源 + */ + addLocaleSrc(json: {[lang: string]: {[l10nId: string]: string}}): void { + l10nHelper.addLocaleSrc(json) + } + + /* 该组件是否启用了国际化功能, 用于判断是否因系统语言变化而刷新 */ + _languageChangeObserve: boolean = false + get _l10n() { + return this._languageChangeObserve + } + + set _l10n(value) { + if (value) { + if (!this._languageChangeObserve) { + l10nHelper.observe(this) + } + } else { + l10nHelper.unobserve(this) + } + this._languageChangeObserve = value + } + /* 根据 id 和传值 ctxdata 获取国际化结果 */ + $l = (id: string, ctxdata?: string) => { + this._l10n = true + return l10nHelper.get(id, ctxdata) + } + public static get styles(): CSSResultArray { return [] } @@ -134,4 +174,4 @@ export class StarBaseElement extends StarMixin(LitElement) { export * from 'lit' export * from 'lit/decorators.js' export * from 'lit/directives/if-defined.js' -export * from 'lit-html/directives/style-map.js' +export * from 'lit/directives/style-map.js' diff --git a/src/components/base/tsconfig.json b/src/components/base/tsconfig.json index f38e3f9..8b6101d 100644 --- a/src/components/base/tsconfig.json +++ b/src/components/base/tsconfig.json @@ -4,5 +4,9 @@ "composite": true, "rootDir": "../../" }, - "include": ["*.ts", "../../lib/gesture/gesture-detector.ts"] + "include": [ + "*.ts", + "../../lib/gesture/gesture-detector.ts", + "../../../../typings/" + ] } diff --git a/src/components/battery-square/battery-square-styles.ts b/src/components/battery-square/battery-square-styles.ts index b5f1436..827301d 100644 --- a/src/components/battery-square/battery-square-styles.ts +++ b/src/components/battery-square/battery-square-styles.ts @@ -1,11 +1,10 @@ import {css, CSSResult} from 'lit' export const sharedStyles: CSSResult = css` - .power { + :host { position: relative; width: 100%; - height: 0; - padding: 0; - padding-bottom: 100%; + height: 100%; + display: block; } .container { @@ -36,10 +35,10 @@ export const sharedStyles: CSSResult = css` position: absolute; font-style: normal; font-weight: 400; - font-size: 9vw; + font-size: var(--percent-size); color: #717171; padding-inline-start: 25%; - padding-top: 2.5%; + padding-block-start: 2.5%; } .lightning { diff --git a/src/components/battery-square/battery-square.ts b/src/components/battery-square/battery-square.ts index dbf2f0c..37523e7 100644 --- a/src/components/battery-square/battery-square.ts +++ b/src/components/battery-square/battery-square.ts @@ -18,6 +18,8 @@ export class StarBatterysquare extends LitElement { @query('.canvas') _canvas: any + @query('.container') _container: any + drawcircle() { const canvas = this._canvas const can = canvas.getContext('2d') @@ -57,19 +59,28 @@ export class StarBatterysquare extends LitElement { can.closePath() } + protected resize() { + var width = this._container.clientWidth + console.log(width) + this.style.setProperty('--percent-size', width / 10 + 'px') + } + + protected firstUpdated(): void { this.drawcircle() this.drawarc() + this.resize() + window.addEventListener('resize', () => { + this.resize() + }) } render() { return html` -
-
- - - % -
+
+ + + %
` } diff --git a/src/components/battery/battery-styles.ts b/src/components/battery/battery-styles.ts index 7cecb8b..64a425c 100644 --- a/src/components/battery/battery-styles.ts +++ b/src/components/battery/battery-styles.ts @@ -1,12 +1,11 @@ import {css, CSSResult} from 'lit' export const sharedStyles: CSSResult = css` - // .holder { - // position: relative; - // width: var(--battery-width, 100%); - // height: 0; - // padding: 0; - // padding-bottom: 35.4%; - // } + :host { + position: relative; + width: 100%; + height: 100%; + display: block; + } .container { display: flex; diff --git a/src/components/clock/clock-style.ts b/src/components/clock/clock-style.ts index da49d89..38d607f 100644 --- a/src/components/clock/clock-style.ts +++ b/src/components/clock/clock-style.ts @@ -1,4 +1,19 @@ -import {css, CSSResult} from 'lit' +import {css, CSSResult,unsafeCSS} from 'lit' +const darkHour = unsafeCSS( + new URL('./svg/dark_hour.svg', import.meta.url).href +) +const darkMinute = unsafeCSS( + new URL('./svg/dark_minute.svg', import.meta.url).href +) +const lightHour = unsafeCSS( + new URL('./svg/light_hour.svg', import.meta.url).href +) +const lightMinute = unsafeCSS( + new URL('./svg/light_minute.svg', import.meta.url).href +) +const sharedSecond = unsafeCSS( + new URL('./svg/second.svg', import.meta.url).href +) export const sharedStyles: CSSResult = css` @media (prefers-color-scheme: light) { :host { @@ -17,13 +32,13 @@ export const sharedStyles: CSSResult = css` --back-clock-backdrop-filter: blur(20px); } .star-clock-hour-hand { - background: url('./src/components/clock/svg/light_hour.svg') no-repeat; + background: url(${lightHour}) no-repeat; } .star-clock-minute-hand { - background: url('./src/components/clock/svg/light_minute.svg') no-repeat; + background: url(${lightMinute}) no-repeat; } .star-clock-second-hand { - background: url('./src/components/clock/svg/second.svg') no-repeat; + background: url(${sharedSecond}) no-repeat; } } @media (prefers-color-scheme: dark) { @@ -42,13 +57,13 @@ export const sharedStyles: CSSResult = css` --back-clock-backdrop-filter: blur(20.3871px); } .star-clock-hour-hand { - background: url('./src/components/clock/svg/dark_hour.svg') no-repeat; + background: url(${darkHour}) no-repeat; } .star-clock-minute-hand { - background: url('./src/components/clock/svg/dark_minute.svg') no-repeat; + background: url(${darkMinute}) no-repeat; } .star-clock-second-hand { - background: url('./src/components/clock/svg/second.svg') no-repeat; + background: url(${sharedSecond}) no-repeat; } } * { diff --git a/src/components/grid-container/container.ts b/src/components/grid-container/container.ts index 8df4c95..045a37a 100644 --- a/src/components/grid-container/container.ts +++ b/src/components/grid-container/container.ts @@ -1608,6 +1608,13 @@ export class GaiaContainer extends StarBaseElement { composed: true, }) ) + } else if (this._dnd.isSpanning) { + this.dispatchEvent( + new CustomEvent('cross-field', { + detail: child, + composed: true, + }) + ) } } diff --git a/src/components/grid-container/element-exchange-strategy.ts b/src/components/grid-container/element-exchange-strategy.ts index a337c82..404e328 100644 --- a/src/components/grid-container/element-exchange-strategy.ts +++ b/src/components/grid-container/element-exchange-strategy.ts @@ -19,6 +19,13 @@ type PlacedRecorder = { * 7. 为满足规则5,规则6的寻位方向变化以顺时针方向转变,如:上左 → 上右 → 下右 → 下左 * 8. 图标节点被另一个图标节点挤占位置时,仅能再去挤占相邻网格的图标的位置(即不允许挤占上下相邻图标) * 9. TODO: 图标被小组件挤压后,优先考虑集体移动到组件的相同的某一个方向上 + * 10. TODO: 被挤占位置时, 比起就近原则再去挤占其他组件或图标的位置, 应优先寻找可放置的空白位置 + * + * 核心准则: + * 1. TODO: 应尽量保证用户放置图标、小组件、应用文件夹的页面、位置不被扰乱 + * 2. 因拖拽、放置的自由度很高, 应尽量降低用户对一次误操作或者一次不满意操作的纠错成本 + * 3. TODO: 非必要不应挪动其他与本次换位无关的元素的位置 + * 4. 除非被主动拖拽, 元素不允许任何跨页放置行为(很明显地, 进行一次跨页操作所需要的时间成本和操作成本远大于页内操作) */ enum Directions { diff --git a/src/components/grid-container/gaia-container-child.ts b/src/components/grid-container/gaia-container-child.ts index ccdcf4a..760839a 100644 --- a/src/components/grid-container/gaia-container-child.ts +++ b/src/components/grid-container/gaia-container-child.ts @@ -65,6 +65,8 @@ export default class GaiaContainerChild { _order: number = -1 // 用于记录将会与其进行交换的占位DOM _switch?: HTMLElement + // 越界标志 + breakout: boolean = false constructor( element: HTMLElement = document.createElement('div'), @@ -453,7 +455,15 @@ export default class GaiaContainerChild { this._lastElementHeight = this.element?.offsetHeight! } + const crossResult = this.manager.exchangeStratege.handleCrossField( + this.curGridId, + this + ) + + this.breakout = crossResult.bottom || crossResult.right + !isActive && + !this.breakout && !this.container.classList.contains('dragging') && (container.style.transform = 'translate(' + left + 'px, ' + top + 'px)') return true diff --git a/src/components/icon-added-container/added-container-child.ts b/src/components/icon-added-container/added-container-child.ts new file mode 100644 index 0000000..3a7543b --- /dev/null +++ b/src/components/icon-added-container/added-container-child.ts @@ -0,0 +1,114 @@ +export default class AddedContainerChild { + _element: HTMLElement | null + _container: HTMLElement | null = null + _master: HTMLElement | null = null + _lastElementWidth: number | null = null + _lastElementHeight: number | null = null + _lastElementDisplay: string | null = null + _lastElementOrder: string | null = null + _lastMasterTop: number | null = null + _lastMasterLeft: number | null = null + // 状态计时器 + removed: number | undefined = undefined + added: number | undefined = undefined + + constructor(element: HTMLElement | null) { + this._element = element + this.markDirty() + } + + get element() { + return this._element + } + + /** + * The element that will contain the child element and control its position. + */ + get container() { + if (!this._container) { + // Create container + let container = document.createElement('div') + container.classList.add('gaia-container-child') + container.style.position = 'absolute' + container.style.top = '0' + container.style.left = '0' + container.appendChild(this.element as HTMLElement) //this.element是div.icon-container + + this._container = container + } + return this._container + } + + /** + * The element that will be added to the container that will + * control the element's transform. + */ + get master() { + if (!this._master) { + // Create master + let master = document.createElement('div') + master.style.visibility = 'hidden' + this._master = master + } + return this._master + } + + /** + * Clears any cached style properties. To be used if elements are + * manipulated outside of the methods of this object. + */ + markDirty() { + this._lastElementWidth = null + this._lastElementHeight = null + this._lastElementDisplay = null + this._lastElementOrder = null + this._lastMasterTop = null + this._lastMasterLeft = null + } + + /** + * Synchronise the size of the master with the managed child element. + */ + synchroniseMaster() { + let master = this.master + let element = this.element + + let style = window.getComputedStyle(element as HTMLElement) + let display = style.display + let order = style.order + let width = (element as HTMLElement).offsetWidth + let height = (element as HTMLElement).offsetHeight + + if ( + this._lastElementWidth !== width || + this._lastElementHeight !== height || + this._lastElementDisplay !== display || + this._lastElementOrder !== order + ) { + this._lastElementWidth = width + this._lastElementHeight = height + this._lastElementDisplay = display + this._lastElementOrder = order + + master.style.width = width + 'px' + master.style.height = height + 'px' + master.style.display = display + master.style.order = order + } + } + + /** + * Synchronise the container's transform with the position of the master. + */ + synchroniseContainer() { + let master = this.master + let container = this.container + let top = master.offsetTop + let left = master.offsetLeft + if (this._lastMasterTop !== top || this._lastMasterLeft !== left) { + this._lastMasterTop = top + this._lastMasterLeft = left + container.style.transform = 'translate(' + left + 'px, ' + top + 'px)' + } + } +} diff --git a/src/components/icon-added-container/icon-added-container.ts b/src/components/icon-added-container/icon-added-container.ts new file mode 100644 index 0000000..7cffe8b --- /dev/null +++ b/src/components/icon-added-container/icon-added-container.ts @@ -0,0 +1,1146 @@ +import {html, LitElement} from 'lit' +import {customElement, property} from 'lit/decorators.js' +import AddedContainerChild from './added-container-child' +// import { +// DragAndDrop +// } from '../grid-container/contianer-interface' + +/** + * The time, in ms, to wait for an animation to start in response to a state + * change, before removing the associated style class. + */ +const STATE_CHANGE_TIMEOUT = 100 + +/** + * The time, in ms, to wait before initiating a drag-and-drop from a + * long-press. + */ +const DEFAULT_DND_TIMEOUT = 300 + +/** + * The distance, in CSS pixels, in which a touch or mouse point can deviate + * before it being discarded for initiation of a drag-and-drop action. + */ +const DND_THRESHOLD = 5 + +/** + * The minimum time between sending move events during drag-and-drop. + */ +const DND_MOVE_THROTTLE = 50 +interface ClickInfo { + pageX: number + pageY: number + clientX: number + clientY: number +} + +interface lastClickInfo extends ClickInfo { + timeStamp: number +} + +interface DragAndDrop { + // Whether drag-and-drop is enabled + enabled: boolean + + // The time, in ms, to wait before initiating a drag-and-drop from a + // long-press + delay: number + + // Timeout used to initiate drag-and-drop actions + timeout: NodeJS.Timeout + + // The child that was tapped/clicked + child: any + + // Whether a drag is active + active: boolean + + // The start point of the drag action + start: ClickInfo + + // The last point of the drag action + last: lastClickInfo + + // Timeout used to send drag-move events + moveTimeout: NodeJS.Timeout + + // The last time a move event was fired + lastMoveEventTime: number + + // Whether to capture the next click event + clickCapture: boolean + + center: { + x: number + y: number + } + + // 定位坐标 + gridPosition: { + x: number + y: number + } +} + +@customElement('icon-added-container') +export class IconAddedContainer extends LitElement { + _frozen: boolean = false + _pendingStateChanges: Function[] = [] + _children: AddedContainerChild[] = [] + _dnd: DragAndDrop = { + // Whether drag-and-drop is enabled + enabled: false, + + // The time, in ms, to wait before initiating a drag-and-drop from a + // long-press + delay: DEFAULT_DND_TIMEOUT, + + // Timeout used to initiate drag-and-drop actions + timeout: null as any, + + // The child that was tapped/clicked + child: null, + + // Whether a drag is active + active: false, + + // The start point of the drag action + start: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + }, + + // The last point of the drag action + last: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + timeStamp: 0, + }, + + center: { + x: 0, + y: 0, + }, + + // 定位坐标 + gridPosition: { + x: 0, + y: 0, + }, + // Timeout used to send drag-move events + moveTimeout: null as any, + + // The last time a move event was fired + lastMoveEventTime: 0, + + // Whether to capture the next click event + clickCapture: false, + } + height: number = 0 + width: number = 0 + top: number = 0 + left: number = 0 + bottom: number = 0 + right: number = 0 + dndObserver: MutationObserver | null = null + + @property({type: String}) id = '' + @property({type: Boolean}) isunadd = false + + firstUpdated() { + let {width, height, top, right, bottom, left} = this.getBoundingClientRect() + this.height = height + this.width = width + this.top = top + this.left = left + this.bottom = bottom + this.right = right + let dndObserverCallback = () => { + if (this._dnd.enabled !== this.dragAndDrop) { + this._dnd.enabled = this.dragAndDrop + if (this._dnd.enabled) { + this.addEventListener('touchstart', this) + this.addEventListener('touchmove', this) + this.addEventListener('touchcancel', this) + this.addEventListener('touchend', this) + // this.addEventListener("mousedown", this); + // this.addEventListener("mousemove", this); + // this.addEventListener("mouseup", this); + this.addEventListener('click', this, true) + this.addEventListener('contextmenu', this, true) + } else { + this.cancelDrag() + this.removeEventListener('touchstart', this) + this.removeEventListener('touchmove', this) + this.removeEventListener('touchcancel', this) + this.removeEventListener('touchend', this) + // this.removeEventListener("mousedown", this); + // this.removeEventListener("mousemove", this); + // this.removeEventListener("mouseup", this); + this.removeEventListener('click', this, true) + this.removeEventListener('contextmenu', this, true) + } + } + } + this.dndObserver = new MutationObserver(dndObserverCallback) + this.dndObserver.observe(this, { + attributes: true, + attributeFilter: ['drag-and-drop'], + }) + + dndObserverCallback() + window.addEventListener('resize', () => { + this.firstUpdated() + }) + } + + get elements() { + //div.icon-container + return this._children.map((child) => { + return child.element + }) + } + + get firstChild() { + return this._children.length ? this._children[0].element : null + } + + get lastChild() { + const length = this._children.length + return length ? this._children[length - 1].element : null + } + + get dragAndDrop() { + return this.getAttribute('drag-and-drop') !== null + } + + get dragAndDropTimeout() { + return this._dnd.delay + } + + set dragAndDropTimeout(timeout) { + if (timeout >= 0) { + this._dnd.delay = timeout + } else { + this._dnd.delay = DEFAULT_DND_TIMEOUT + } + } + + getChildByElement(element: HTMLElement) { + let children = [...this._children] + for (const child of children) { + if ( + child._element === element || + child.master === element || + child.container === element + ) { + return child + } + } + + return null + } + + realAppendChild(element: HTMLElement) { + let proto = HTMLElement.prototype.appendChild + let func = proto.call(this, element) + return func + } + + realRemoveChild(element: HTMLElement) { + let proto = HTMLElement.prototype.removeChild + let func = proto.call(this, element) + return func + } + + realInsertBefore(...args: [HTMLElement, HTMLElement]) { + let proto = HTMLElement.prototype.insertBefore + let func = proto.apply(this, args) + return func + } + + realReplaceChild(...args: [HTMLElement, HTMLElement]) { + let proto = HTMLElement.prototype.replaceChild + let func = proto.apply(this, args) + return func + } + + appendContainerChild(element: HTMLElement, callback: Function) { + this.insertContainerBefore(element, null, callback) + } + + clearAll() { + this.elements.forEach((el) => { + this.removeContainerChild(el as HTMLElement) + }) + } + + removeContainerChild(element: HTMLElement, callback?: Function) { + let children = this._children + let childToRemove: AddedContainerChild | null = null + + for (let child of children) { + if (child.element === element) { + childToRemove = child + break + } + } + + if (childToRemove === null) { + throw 'removeChild called on unknown child' + } + + this.changeState(childToRemove, 'removed', () => { + this.realRemoveChild((childToRemove as AddedContainerChild).container) + this.realRemoveChild((childToRemove as AddedContainerChild).master) + + // Find the child again. We need to do this in case the container was + // manipulated between removing the child and this callback being reached. + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === childToRemove) { + children.splice(i, 1) + break + } + } + + if (callback) { + callback() + } + + this.synchronise() + }) + } + + replaceContainerChild( + newElement: HTMLElement, + oldElement: HTMLElement, + callback: Function + ) { + if (!newElement || !oldElement) { + throw 'replaceChild called with null arguments' + } + + // Unparent the newElement if necessary (with support for gaia-container) + if (newElement.parentNode) { + this.removeContainerChild(newElement, () => { + this.replaceContainerChild(newElement, oldElement, callback) + }) + if (newElement.parentNode) { + return + } + } + + // Remove the old child and add the new one, but don't run the removed/ + // added state changes. + let children = this._children + + for (let i = 0, iLen = children.length; i < iLen; i++) { + let oldChild = children[i] + if (oldChild.element === oldElement) { + let newChild = new AddedContainerChild(newElement) + this.realInsertBefore(newChild.container, oldChild.container) + this.realInsertBefore(newChild.master, oldChild.master) + this.realRemoveChild(oldChild.container) + this.realRemoveChild(oldChild.master) + this._children.splice(i, 1, newChild) + this.synchronise() + + if (callback) { + callback() + } + return + } + } + + throw 'removeChild called on unknown child' + } + + /** + * Reorders the given element to appear before referenceElement. + */ + reorderChild( + element: HTMLElement, + referenceElement: HTMLElement, + callback: Function + ) { + if (!element) { + throw 'reorderChild called with null element' + } + + let children = this._children + let child = null + let childIndex = null + let referenceChild = null + let referenceChildIndex = null + + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === element) { + child = children[i] + childIndex = i + } else if (children[i].element === referenceElement) { + referenceChild = children[i] + referenceChildIndex = i + } + + if (child && (referenceChild || !referenceElement)) { + this.realRemoveChild(child.container) + this.realRemoveChild(child.master) + children.splice(childIndex as number, 1) + + if (referenceChild) { + this.realInsertBefore(child.container, referenceChild.container) + this.realInsertBefore(child.master, referenceChild.master) + } else { + children.length === 0 + ? this.realAppendChild(child.container) + : this.realInsertBefore(child.container, children[0].master) + this.realAppendChild(child.master) + } + + referenceChild + ? children.splice( + (referenceChildIndex as number) - + ((childIndex as number) < (referenceChildIndex as number) + ? 1 + : 0), + 0, + child + ) + : children.splice(children.length, 0, child) + + this.synchronise() + + if (callback) { + callback() + } + return + } + } + + throw child + ? 'reorderChild called on unknown reference element' + : 'reorderChild called on unknown child' + } + + insertContainerBefore( + element: HTMLElement, + reference: HTMLElement | null, + callback: Function + ) { + let children = this._children + let childToInsert = new AddedContainerChild(element) + let referenceIndex = -1 + + if (reference !== null) { + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === reference) { + referenceIndex = i + break + } + } + if (referenceIndex === -1) { + throw 'insertBefore called on unknown child' + } + } + + if (referenceIndex === -1) { + children.length === 0 + ? this.realAppendChild(childToInsert.container) + : this.realInsertBefore(childToInsert.container, children[0].master) + this.realAppendChild(childToInsert.master) + children.push(childToInsert) + } else { + this.realInsertBefore( + childToInsert.container, + children[referenceIndex].container + ) + this.realInsertBefore( + childToInsert.master, + children[referenceIndex].master + ) + children.splice(referenceIndex, 0, childToInsert) + } + + this.changeState(childToInsert, 'added', callback) + this.synchronise() + } + + /** + * Used to execute a state-change of a child that may possibly be animated. + * @state will be added to the child's class-list. If an animation starts that + * has the same name, that animation will complete and @callback will be + * called. Otherwise, the class will be removed and @callback called on the + * next frame. + */ + changeState( + child: AddedContainerChild, + state: 'added' | 'removed', + callback: Function + ) { + // Check that the child is still attached to this parent (can happen if + // the child is removed while frozen). + if ((child.container as any).parentNode !== this) { + return + } + + // Check for a redundant state change. + if (child.container.classList.contains(state)) { + return + } + + // Queue up state change if we're frozen. + if (this._frozen) { + this._pendingStateChanges.push( + this.changeState.bind(this, child, state, callback) + ) + return + } + + let animStart = (e: AnimationEvent) => { + if (!e.animationName.endsWith(state)) { + return + } + + child.container.removeEventListener('animationstart', animStart) + + window.clearTimeout(child[state]) + delete child[state] + + // let self = this; + child.container.addEventListener('animationend', function animEnd() { + child.container.removeEventListener('animationend', animEnd) + child.container.classList.remove(state) + if (callback) { + callback() + } + }) + } + + child.container.addEventListener('animationstart', animStart) + child.container.classList.add(state) + + child[state] = window.setTimeout(() => { + delete child[state] + child.container.removeEventListener('animationstart', animStart) + child.container.classList.remove(state) + if (callback) { + callback() + } + }, STATE_CHANGE_TIMEOUT) + } + + getChildOffsetRect(element: HTMLElement) { + let children = this._children + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i] + if (child.element === element) { + let top = child._lastMasterTop as number + let left = child._lastMasterLeft as number + let width = child._lastElementWidth as number + let height = child._lastElementHeight as number + + return { + top: top, + left: left, + width: width, + height: height, + right: left + width, + bottom: top + height, + } + } + } + + throw 'getChildOffsetRect called on unknown child' + } + + getChildFromPoint(x: number, y: number) { + let children = this._children + for ( + let parent = this.parentElement; + parent; + parent = parent.parentElement + ) { + x += parent.scrollLeft - parent.offsetLeft + y += parent.scrollTop - parent.offsetTop + } + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i] + if ( + x >= (child._lastMasterLeft as number) && + y >= (child._lastMasterTop as number) && + x < + (child._lastMasterLeft as number) + + (child._lastElementWidth as number) && + y < + (child._lastMasterTop as number) + + (child._lastElementHeight as number) + ) { + return child.element + } + } + + return null + } + + cancelDrag() { + if (this._dnd.timeout !== null) { + clearTimeout(this._dnd.timeout) + this._dnd.timeout = null as any + } + + if (this._dnd.moveTimeout !== null) { + clearTimeout(this._dnd.moveTimeout) + this._dnd.moveTimeout = null as any + } + + if (this._dnd.active) { + this._dnd.child.container.classList.remove('dragging') + this._dnd.child.container.style.position = 'absolute' + this._dnd.child.container.style.top = '0' + this._dnd.child.container.style.left = '0' + this._dnd.child.markDirty() + this._dnd.child = null + this._dnd.active = false + this.synchronise() + this._dnd.clickCapture = true + this.dispatchEvent(new CustomEvent('drag-added-finish', {composed: true})) + } + } + + startDrag() { + const child = this._dnd.child + this._dnd.gridPosition.x = + child._lastMasterLeft + child.master.offsetWidth / 2 + this._dnd.gridPosition.y = + child._lastMasterTop + child.master.offsetHeight / 2 + + this._dnd.center.x = child._lastMasterLeft + child.master.offsetWidth / 2 + this._dnd.center.y = child._lastMasterTop + child.master.offsetHeight / 2 + if ( + !this.dispatchEvent( + new CustomEvent('drag-added-start', { + cancelable: true, + detail: { + target: this._dnd.child.element, + pageX: this._dnd.start.pageX, + pageY: this._dnd.start.pageY, + clientX: this._dnd.start.clientX, + clientY: this._dnd.start.clientY, + }, + composed: true, + }) + ) + ) { + return + } + + this._dnd.active = true + this._dnd.child.container.classList.add('dragging') + this._dnd.child.container.style.position = 'fixed' + let rect = this.getBoundingClientRect() + this._dnd.child.container.style.top = rect.top + 'px' + this._dnd.child.container.style.left = rect.left + 'px' + } + + continueDrag() { + if (!this._dnd.active) { + return + } + + let left = + this._dnd.child.master.offsetLeft + + (this._dnd.last.pageX - this._dnd.start.pageX) + let top = + this._dnd.child.master.offsetTop + + (this._dnd.last.pageY - this._dnd.start.pageY) + this._dnd.child.container.style.transform = + 'translate(' + left + 'px, ' + top + 'px)' + + if (this._dnd.moveTimeout === null) { + let delay = Math.max( + 0, + DND_MOVE_THROTTLE - + (this._dnd.last.timeStamp - this._dnd.lastMoveEventTime) + ) + this._dnd.moveTimeout = setTimeout(() => { + this._dnd.moveTimeout = null as any + this._dnd.lastMoveEventTime = this._dnd.last.timeStamp + this.dispatchEvent( + new CustomEvent('drag-added-move', { + detail: { + target: this._dnd.child.element, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY, + }, + composed: true, + }) + ) + }, delay) + } + } + + endDrag(event: Event) { + if (this._dnd.active) { + const centerX = + this.left + + this._dnd.center.x + + (this._dnd.last.pageX - this._dnd.start.pageX) + const centerY = + this.top + + this._dnd.center.y + + (this._dnd.last.pageY - this._dnd.start.pageY) + const elementHeight = this._dnd.child.element.offsetHeight + const elementWidth = this._dnd.child.element.offsetWidth + let dropTarget = this.getChildFromPoint( + this._dnd.last.clientX, + this._dnd.last.clientY + ) + + if ( + this.dispatchEvent( + new CustomEvent('drag-added-end', { + cancelable: true, + detail: { + target: this._dnd.child.element, + dropTarget: dropTarget, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY, + centerX, + centerY, + gridHeight: elementHeight, + gridWidth: elementWidth, + }, + composed: true, + }) + ) + ) { + let children = this._children + + if (dropTarget && dropTarget !== this._dnd.child.element) { + let dropChild: AddedContainerChild | null = null + let dropIndex = -1 + let childIndex = -1 + let insertBefore = true + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === this._dnd.child) { + childIndex = i + if (!dropChild) { + insertBefore = false + } + } + + if (children[i].element === dropTarget) { + dropChild = children[i] + dropIndex = i + } + + if (dropIndex >= 0 && childIndex >= 0) { + break + } + } + + if (dropIndex >= 0 && childIndex >= 0) { + // Default action, rearrange the dragged child to before or after + // the child underneath the touch/mouse point. + this.realRemoveChild(this._dnd.child.container) + this.realRemoveChild(this._dnd.child.master) + this.realInsertBefore( + this._dnd.child.container, + insertBefore + ? ((dropChild as AddedContainerChild).container as any) + : (dropChild as AddedContainerChild).container.nextSibling + ) + this.realInsertBefore( + this._dnd.child.master, + insertBefore + ? ((dropChild as AddedContainerChild).master as any) + : (dropChild as AddedContainerChild).master.nextSibling + ) + children.splice(dropIndex, 0, children.splice(childIndex, 1)[0]) + this.dispatchEvent( + new CustomEvent('drag-added-rearrange', {composed: true}) + ) + } + } + } + } else if (this._dnd.timeout !== null) { + let handled = !this.dispatchEvent( + new CustomEvent('added-activate', { + cancelable: true, + composed: true, + detail: { + target: this._dnd.child.element, + }, + }) + ) + if (handled) { + event.stopImmediatePropagation() + event.preventDefault() + } + } + + this.cancelDrag() + } + + // 节流阀 + throttle: number | undefined = undefined + // 拖拽节点所处位置 + dragPosition: 'inner' | 'outter' = 'inner' + handleEvent(event: Event) { + switch (event.type) { + case 'touchstart': + case 'mousedown': + if (this._dnd.active || this._dnd.timeout) { + // this.cancelDrag() + // break + } + + if (event instanceof MouseEvent) { + this._dnd.start.pageX = event.pageX + this._dnd.start.pageY = event.pageY + this._dnd.start.clientX = event.clientX + this._dnd.start.clientY = event.clientY + } else { + this._dnd.start.pageX = (event as TouchEvent).touches[0].pageX + this._dnd.start.pageY = (event as TouchEvent).touches[0].pageY + this._dnd.start.clientX = (event as TouchEvent).touches[0].clientX + this._dnd.start.clientY = (event as TouchEvent).touches[0].clientY + } + + this._dnd.last.pageX = this._dnd.start.pageX + this._dnd.last.pageY = this._dnd.start.pageY + this._dnd.last.clientX = this._dnd.start.clientX + this._dnd.last.clientY = this._dnd.start.clientY + this._dnd.last.timeStamp = event.timeStamp + + let target = event.target as any //gaia-app-icon + + for (; target.parentNode !== this; target = target.parentNode) { + if (target === this || !target.parentNode) { + return + } + } + + // Find the child + let children = this._children + for (let child of children) { + if (child.container === target) { + this._dnd.child = child + break + } + } + + if (!this._dnd.child) { + return + } + + if (this._dnd.delay > 0) { + this._dnd.timeout = setTimeout(() => { + this._dnd.timeout = null as any + this.startDrag() + }, this._dnd.delay) + } else { + this.startDrag() + } + break + + case 'touchmove': + case 'mousemove': + // event.preventDefault(); + // event.stopPropagation(); + let pageX, pageY, clientX, clientY + if (event instanceof MouseEvent) { + pageX = event.pageX + pageY = event.pageY + clientX = event.clientX + clientY = event.clientY + } else { + pageX = (event as TouchEvent).touches[0].pageX + pageY = (event as TouchEvent).touches[0].pageY + clientX = (event as TouchEvent).touches[0].clientX + clientY = (event as TouchEvent).touches[0].clientY + } + + if (this._dnd.timeout) { + if ( + Math.abs((pageX as number) - this._dnd.start.pageX) > + DND_THRESHOLD || + Math.abs((pageY as number) - this._dnd.start.pageY) > DND_THRESHOLD + ) { + clearTimeout(this._dnd.timeout) + this._dnd.timeout = null as any + } + } else if (this._dnd.active) { + event.preventDefault() + this._dnd.last.pageX = pageX as number + this._dnd.last.pageY = pageY as number + this._dnd.last.clientX = clientX as number + this._dnd.last.clientY = clientY as number + this._dnd.last.timeStamp = event.timeStamp + + this.continueDrag() + } + + const centerX = + this._dnd.center.x + + (this._dnd.last.pageX - this._dnd.start.pageX) + + this.left + const centerY = + this._dnd.center.y + + (this._dnd.last.pageY - this._dnd.start.pageY) + + this.top + const elementHeight = this._dnd.child.element.offsetHeight + const elementWidth = this._dnd.child.element.offsetWidth + if (centerY > this.offsetHeight) { + this.dragPosition = 'outter' + if (!this.throttle) { + this.throttle = window.setTimeout(() => { + this.dispatchEvent( + new CustomEvent('drag-outside-added-container', { + detail: { + element: this._dnd.child.element, + gridHeight: elementHeight, + gridWidth: elementWidth, + centerX: centerX, + centerY: centerY, + }, + composed: true, + }) + ) + + this.throttle = undefined + }, 100) + } + } else { + if (this.dragPosition == 'outter') { + this.dragPosition = 'inner' + // this.dispatchEvent( + // new CustomEvent('drag-into-added-container', { + // detail: this._dnd.child.element, + // composed: true, + // }) + // ) + } + clearTimeout(this.throttle) + this.throttle = undefined + } + break + + case 'touchcancel': + this.cancelDrag() + break + + case 'touchend': + case 'mouseup': + if (this._dnd.active) { + event.preventDefault() + event.stopImmediatePropagation() + } + this.endDrag(event) + break + + case 'click': + if (this._dnd.clickCapture) { + this._dnd.clickCapture = false + event.preventDefault() + event.stopImmediatePropagation() + } + break + + case 'contextmenu': + if (this._dnd.active || this._dnd.timeout) { + event.stopImmediatePropagation() + // event.preventDefault(); + } + break + } + } + + /** + * Temporarily disables element position synchronisation. Useful when adding + * multiple elements to the container at the same time, or in quick + * succession. + */ + freeze() { + this._frozen = true + } + + /** + * Enables element position synchronisation after freeze() has been called. + */ + thaw() { + if (this._frozen) { + this._frozen = false + for (let callback of this._pendingStateChanges) { + callback() + } + this._pendingStateChanges = [] + this.synchronise() + } + } + + /** + * Synchronise positions between the managed container and all children. + * This is called automatically when adding/inserting or removing children, + * but must be called manually if the managed container is manipulated + * outside of these methods (for example, if style properties change, or + * if it's resized). + */ + synchronise() { + if (this._frozen) { + return + } + + let child + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseMaster() + } + } + + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseContainer() + } + } + } + + dragIn( + dragInfo?: { + element: HTMLElement + clientX: number + clientY: number + }, + callback?: Function + ) { + if (dragInfo) { + let {element, clientX, clientY} = dragInfo + let child = + element instanceof AddedContainerChild + ? element + : this.getChildByElement(element) + if (!child) { + this._dnd.child = new AddedContainerChild(element) + } + + let dropTarget = this.getChildFromPoint(clientX, clientY) + let children = this._children + let lastIndex = this._children.length + let lastChild = this._children[lastIndex - 1] + + if (dropTarget && dropTarget !== this._dnd.child.element) { + let dropChild: AddedContainerChild | null = null + let dropIndex = -1 + let childIndex = -1 + let insertBefore = true + for (let i = 0, iLen = children.length; i < iLen; i++) { + childIndex = i + if (!dropChild) { + insertBefore = false + } + + if (children[i].element === dropTarget) { + dropChild = children[i] + dropIndex = i + } + + if (dropIndex >= 0 && childIndex >= 0) { + break + } + } + + if (dropIndex >= 0 && dropIndex < children.length - 2) { + // Default action, rearrange the dragged child to before or after + // the child underneath the touch/mouse point. + this.realInsertBefore( + this._dnd.child.container, + insertBefore + ? ((dropChild as AddedContainerChild).container as any) + : (dropChild as AddedContainerChild).container.nextSibling + ) + this.realInsertBefore( + this._dnd.child.master, + insertBefore + ? ((dropChild as AddedContainerChild).master as any) + : (dropChild as AddedContainerChild).master.nextSibling + ) + + insertBefore + ? children.splice(dropIndex, 0, this._dnd.child) + : children.splice(dropIndex + 1, 0, this._dnd.child) + let x = dropChild?.master.offsetTop + let y = dropChild?.master.offsetLeft + this._dnd.child.container.style.transform = `translate(${x}px, ${y}px)` + } + } else { + // dropTaget为null(在最后一个元素附近) 或dropTarget为最后一个元素 + let top = lastChild.master.offsetTop + let left = lastChild.master.offsetLeft + let newTop = null + let newLeft = null + let lastChildClient = lastChild.master.getBoundingClientRect() + let firstChildClient = children[0].master.getBoundingClientRect() + let py = lastIndex % 4 + if ( + Math.abs(clientX - lastChildClient.right) < + lastChild.master.offsetWidth / 2 && + Math.abs(clientY - lastChildClient.bottom) < + lastChild.master.offsetHeight / 2 && + py + ) { + // 最后一个元素在某一行的第1/2/3位置,下一个元素的位置 + newTop = top + newLeft = left + lastChild.master.offsetWidth + } + + if ( + Math.abs(clientX - firstChildClient.right) < + lastChild.master.offsetWidth / 2 && + Math.abs(clientY - lastChildClient.bottom) < + lastChild.master.offsetHeight / 2 && + py == 0 + ) { + // 最后一个元素在某一行的第4位置,下一个元素的位置在新的一行的第一个 + newTop = top + lastChild.master.offsetHeight + newLeft = 0 + } + + if (newLeft !== null && newTop !== null) { + this._dnd.child.container.style.transform = `translate(${newTop}px, ${newLeft}px)` + this.realInsertBefore(this._dnd.child.container, children[0].master) + this.realAppendChild(this._dnd.child.master) + children.push(this._dnd.child) + } else { + return + } + } + // this.dispatchEvent(new CustomEvent('drag-added-rearrange', {composed: true})) + this.synchronise() + if (callback) { + callback() + } + } + } + + render() { + return html` +
+ +
+ ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'icon-added-container': IconAddedContainer + } +} diff --git a/src/components/icon-added-container/index.ts b/src/components/icon-added-container/index.ts new file mode 100644 index 0000000..7a947fe --- /dev/null +++ b/src/components/icon-added-container/index.ts @@ -0,0 +1 @@ +export * from './icon-added-container.js' diff --git a/src/components/icon-added-container/package.json b/src/components/icon-added-container/package.json new file mode 100644 index 0000000..cca6475 --- /dev/null +++ b/src/components/icon-added-container/package.json @@ -0,0 +1,22 @@ +{ + "name": "@star-web-components/icon-added-container", + "version": "0.0.1", + "description": "", + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": { + "default": "./index.js" + }, + "./index": { + "default": "./index.js" + }, + "./icon-added-container.js": { + "default": "./icon-added-container.js" + }, + "./package.json": "./package.json" + }, + "author": "", + "license": "ISC" +} diff --git a/src/components/icon-added-container/tsconfig.json b/src/components/icon-added-container/tsconfig.json new file mode 100644 index 0000000..4ba3e2c --- /dev/null +++ b/src/components/icon-added-container/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["*.ts"] +} diff --git a/src/components/icon-unadd-container/container-style.ts b/src/components/icon-unadd-container/container-style.ts new file mode 100644 index 0000000..fdf883b --- /dev/null +++ b/src/components/icon-unadd-container/container-style.ts @@ -0,0 +1,19 @@ +import {css} from 'lit' + +export default css` + :host { + // position: relative; + display: flex; + flex: 1; + margin-bottom: 0; + width: 100%; + height: 100%; + } + ::slotted(.gaia-container-child):not(.dragging):not(.added) { + transition: transform 0.2s; + } + ::slotted(.gaia-container-child.dragging) { + z-index: 1; + will-change: transform; + } +` diff --git a/src/components/icon-unadd-container/icon-unadd-container.ts b/src/components/icon-unadd-container/icon-unadd-container.ts new file mode 100644 index 0000000..6841cd2 --- /dev/null +++ b/src/components/icon-unadd-container/icon-unadd-container.ts @@ -0,0 +1,1013 @@ +import {html, LitElement} from 'lit' +import {customElement, property} from 'lit/decorators.js' +import UnaddContainerChild from './unadd-container-child' + +/** + * The time, in ms, to wait for an animation to start in response to a state + * change, before removing the associated style class. + */ +const STATE_CHANGE_TIMEOUT = 100 + +/** + * The time, in ms, to wait before initiating a drag-and-drop from a + * long-press. + */ +const DEFAULT_DND_TIMEOUT = 300 + +/** + * The distance, in CSS pixels, in which a touch or mouse point can deviate + * before it being discarded for initiation of a drag-and-drop action. + */ +const DND_THRESHOLD = 5 + +/** + * The minimum time between sending move events during drag-and-drop. + */ +const DND_MOVE_THROTTLE = 50 +interface ClickInfo { + pageX: number + pageY: number + clientX: number + clientY: number +} + +interface lastClickInfo extends ClickInfo { + timeStamp: number +} + +interface DragAndDrop { + // Whether drag-and-drop is enabled + enabled: boolean + + // The time, in ms, to wait before initiating a drag-and-drop from a + // long-press + delay: number + + // Timeout used to initiate drag-and-drop actions + timeout: NodeJS.Timeout + + // The child that was tapped/clicked + child: any + + // Whether a drag is active + active: boolean + + // The start point of the drag action + start: ClickInfo + + // The last point of the drag action + last: lastClickInfo + + // Timeout used to send drag-move events + moveTimeout: NodeJS.Timeout + + // The last time a move event was fired + lastMoveEventTime: number + + // Whether to capture the next click event + clickCapture: boolean + + center: { + x: number + y: number + } +} + +@customElement('icon-unadd-container') +export class IconUnaddContainer extends LitElement { + _frozen: boolean = false + _pendingStateChanges: Function[] = [] + _children: UnaddContainerChild[] = [] + _dnd: DragAndDrop = { + // Whether drag-and-drop is enabled + enabled: false, + + // The time, in ms, to wait before initiating a drag-and-drop from a + // long-press + delay: DEFAULT_DND_TIMEOUT, + + // Timeout used to initiate drag-and-drop actions + timeout: null as any, + + // The child that was tapped/clicked + child: null, + + // Whether a drag is active + active: false, + + // The start point of the drag action + start: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + }, + + // The last point of the drag action + last: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + timeStamp: 0, + }, + + center: { + x: 0, + y: 0, + }, + // Timeout used to send drag-move events + moveTimeout: null as any, + + // The last time a move event was fired + lastMoveEventTime: 0, + + // Whether to capture the next click event + clickCapture: false, + } + height: number = 0 + width: number = 0 + top: number = 0 + left: number = 0 + bottom: number = 0 + right: number = 0 + dndObserver: MutationObserver | null = null + + @property({type: String}) id = '' + @property({type: Boolean}) isunadd = false + + firstUpdated() { + let {width, height, top, right, bottom, left} = this.getBoundingClientRect() + this.height = height + this.width = width + this.top = top + this.left = left + this.bottom = bottom + this.right = right + let dndObserverCallback = () => { + if (this._dnd.enabled !== this.dragAndDrop) { + this._dnd.enabled = this.dragAndDrop + if (this._dnd.enabled) { + this.addEventListener('touchstart', this) + this.addEventListener('touchmove', this) + this.addEventListener('touchcancel', this) + this.addEventListener('touchend', this) + // this.addEventListener("mousedown", this); + // this.addEventListener("mousemove", this); + // this.addEventListener("mouseup", this); + this.addEventListener('click', this, true) + this.addEventListener('contextmenu', this, true) + } else { + this.cancelDrag() + this.removeEventListener('touchstart', this) + this.removeEventListener('touchmove', this) + this.removeEventListener('touchcancel', this) + this.removeEventListener('touchend', this) + // this.removeEventListener("mousedown", this); + // this.removeEventListener("mousemove", this); + // this.removeEventListener("mouseup", this); + this.removeEventListener('click', this, true) + this.removeEventListener('contextmenu', this, true) + } + } + } + this.dndObserver = new MutationObserver(dndObserverCallback) + this.dndObserver.observe(this, { + attributes: true, + attributeFilter: ['drag-and-drop'], + }) + + dndObserverCallback() + window.addEventListener('resize', () => { + this.firstUpdated() + }) + } + + get elements() { + //div.icon-container + return this._children.map((child) => { + return child.element + }) + } + + get firstChild() { + return this._children.length ? this._children[0].element : null + } + + get lastChild() { + const length = this._children.length + return length ? this._children[length - 1].element : null + } + + get dragAndDrop() { + return this.getAttribute('drag-and-drop') !== null + } + + get dragAndDropTimeout() { + return this._dnd.delay + } + + set dragAndDropTimeout(timeout) { + if (timeout >= 0) { + this._dnd.delay = timeout + } else { + this._dnd.delay = DEFAULT_DND_TIMEOUT + } + } + + getChildByElement(element: HTMLElement) { + let children = [...this._children] + for (const child of children) { + if ( + child._element === element || + child.master === element || + child.container === element + ) { + return child + } + } + + return null + } + + realAppendChild(element: HTMLElement) { + let proto = HTMLElement.prototype.appendChild + let func = proto.call(this, element) + return func + } + + realRemoveChild(element: HTMLElement) { + let proto = HTMLElement.prototype.removeChild + let func = proto.call(this, element) + return func + } + + realInsertBefore(...args: [HTMLElement, HTMLElement]) { + let proto = HTMLElement.prototype.insertBefore + let func = proto.apply(this, args) + return func + } + + realReplaceChild(...args: [HTMLElement, HTMLElement]) { + let proto = HTMLElement.prototype.replaceChild + let func = proto.apply(this, args) + return func + } + + appendContainerChild(element: HTMLElement, callback: Function) { + this.insertContainerBefore(element, null, callback) + } + + clearAll() { + this.elements.forEach((el) => { + this.removeContainerChild(el as HTMLElement) + }) + } + + removeContainerChild(element: HTMLElement, callback?: Function) { + let children = this._children + let childToRemove: UnaddContainerChild | null = null + + for (let child of children) { + if (child.element === element) { + childToRemove = child + break + } + } + + if (childToRemove === null) { + throw 'removeChild called on unknown child' + } + + this.changeState(childToRemove, 'removed', () => { + this.realRemoveChild((childToRemove as UnaddContainerChild).container) + this.realRemoveChild((childToRemove as UnaddContainerChild).master) + + // Find the child again. We need to do this in case the container was + // manipulated between removing the child and this callback being reached. + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === childToRemove) { + children.splice(i, 1) + break + } + } + + if (callback) { + callback() + } + + this.synchronise() + }) + } + + replaceContainerChild( + newElement: HTMLElement, + oldElement: HTMLElement, + callback: Function + ) { + if (!newElement || !oldElement) { + throw 'replaceChild called with null arguments' + } + + // Unparent the newElement if necessary (with support for gaia-container) + if (newElement.parentNode) { + this.removeContainerChild(newElement, () => { + this.replaceContainerChild(newElement, oldElement, callback) + }) + if (newElement.parentNode) { + return + } + } + + // Remove the old child and add the new one, but don't run the removed/ + // added state changes. + let children = this._children + + for (let i = 0, iLen = children.length; i < iLen; i++) { + let oldChild = children[i] + if (oldChild.element === oldElement) { + let newChild = new UnaddContainerChild(newElement) + this.realInsertBefore(newChild.container, oldChild.container) + this.realInsertBefore(newChild.master, oldChild.master) + this.realRemoveChild(oldChild.container) + this.realRemoveChild(oldChild.master) + this._children.splice(i, 1, newChild) + this.synchronise() + + if (callback) { + callback() + } + return + } + } + + throw 'removeChild called on unknown child' + } + + /** + * Reorders the given element to appear before referenceElement. + */ + reorderChild( + element: HTMLElement, + referenceElement: HTMLElement, + callback: Function + ) { + if (!element) { + throw 'reorderChild called with null element' + } + + let children = this._children + let child = null + let childIndex = null + let referenceChild = null + let referenceChildIndex = null + + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === element) { + child = children[i] + childIndex = i + } else if (children[i].element === referenceElement) { + referenceChild = children[i] + referenceChildIndex = i + } + + if (child && (referenceChild || !referenceElement)) { + this.realRemoveChild(child.container) + this.realRemoveChild(child.master) + children.splice(childIndex as number, 1) + + if (referenceChild) { + this.realInsertBefore(child.container, referenceChild.container) + this.realInsertBefore(child.master, referenceChild.master) + } else { + children.length === 0 + ? this.realAppendChild(child.container) + : this.realInsertBefore(child.container, children[0].master) + this.realAppendChild(child.master) + } + + referenceChild + ? children.splice( + (referenceChildIndex as number) - + ((childIndex as number) < (referenceChildIndex as number) + ? 1 + : 0), + 0, + child + ) + : children.splice(children.length, 0, child) + + this.synchronise() + + if (callback) { + callback() + } + return + } + } + + throw child + ? 'reorderChild called on unknown reference element' + : 'reorderChild called on unknown child' + } + + insertContainerBefore( + element: HTMLElement, + reference: HTMLElement | null, + callback: Function + ) { + let children = this._children + let childToInsert = new UnaddContainerChild(element) + let referenceIndex = -1 + + if (reference !== null) { + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === reference) { + referenceIndex = i + break + } + } + if (referenceIndex === -1) { + throw 'insertBefore called on unknown child' + } + } + + if (referenceIndex === -1) { + children.length === 0 + ? this.realAppendChild(childToInsert.container) + : this.realInsertBefore(childToInsert.container, children[0].master) + this.realAppendChild(childToInsert.master) + children.push(childToInsert) + } else { + this.realInsertBefore( + childToInsert.container, + children[referenceIndex].container + ) + this.realInsertBefore( + childToInsert.master, + children[referenceIndex].master + ) + children.splice(referenceIndex, 0, childToInsert) + } + + this.changeState(childToInsert, 'added', callback) + this.synchronise() + } + + /** + * Used to execute a state-change of a child that may possibly be animated. + * @state will be added to the child's class-list. If an animation starts that + * has the same name, that animation will complete and @callback will be + * called. Otherwise, the class will be removed and @callback called on the + * next frame. + */ + changeState( + child: UnaddContainerChild, + state: 'added' | 'removed', + callback: Function + ) { + // Check that the child is still attached to this parent (can happen if + // the child is removed while frozen). + if ((child.container as any).parentNode !== this) { + return + } + + // Check for a redundant state change. + if (child.container.classList.contains(state)) { + return + } + + // Queue up state change if we're frozen. + if (this._frozen) { + this._pendingStateChanges.push( + this.changeState.bind(this, child, state, callback) + ) + return + } + + let animStart = (e: AnimationEvent) => { + if (!e.animationName.endsWith(state)) { + return + } + + child.container.removeEventListener('animationstart', animStart) + + window.clearTimeout(child[state]) + delete child[state] + + child.container.addEventListener('animationend', function animEnd() { + child.container.removeEventListener('animationend', animEnd) + child.container.classList.remove(state) + if (callback) { + callback() + } + }) + } + + child.container.addEventListener('animationstart', animStart) + child.container.classList.add(state) + + child[state] = window.setTimeout(() => { + delete child[state] + child.container.removeEventListener('animationstart', animStart) + child.container.classList.remove(state) + if (callback) { + callback() + } + }, STATE_CHANGE_TIMEOUT) + } + + getChildOffsetRect(element: HTMLElement) { + let children = this._children + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i] + if (child.element === element) { + let top = child._lastMasterTop as number + let left = child._lastMasterLeft as number + let width = child._lastElementWidth as number + let height = child._lastElementHeight as number + + return { + top: top, + left: left, + width: width, + height: height, + right: left + width, + bottom: top + height, + } + } + } + + throw 'getChildOffsetRect called on unknown child' + } + + getChildFromPoint(x: number, y: number) { + let children = this._children + for ( + let parent = this.parentElement; + parent; + parent = parent.parentElement + ) { + x += parent.scrollLeft - parent.offsetLeft + y += parent.scrollTop - parent.offsetTop + } + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i] + if ( + x >= (child._lastMasterLeft as number) && + y >= (child._lastMasterTop as number) && + x < + (child._lastMasterLeft as number) + + (child._lastElementWidth as number) && + y < + (child._lastMasterTop as number) + + (child._lastElementHeight as number) + ) { + return child.element + } + } + + return null + } + + cancelDrag() { + if (this._dnd.timeout !== null) { + clearTimeout(this._dnd.timeout) + this._dnd.timeout = null as any + } + + if (this._dnd.moveTimeout !== null) { + clearTimeout(this._dnd.moveTimeout) + this._dnd.moveTimeout = null as any + } + + if (this._dnd.active) { + this._dnd.child.container.classList.remove('dragging') + this._dnd.child.container.style.position = 'absolute' + this._dnd.child.container.style.top = '0' + this._dnd.child.container.style.left = '0' + this._dnd.child.markDirty() + this._dnd.child = null + this._dnd.active = false + this.synchronise() + this._dnd.clickCapture = true + this.dispatchEvent(new CustomEvent('drag-unadd-finish', {composed: true})) + } + } + + startDrag() { + const child = this._dnd.child + this._dnd.center.x = child._lastMasterLeft + child.master.offsetWidth / 2 + this._dnd.center.y = child._lastMasterTop + child.master.offsetHeight / 2 + if ( + !this.dispatchEvent( + new CustomEvent('drag-unadd-start', { + cancelable: true, + detail: { + target: this._dnd.child.element, + pageX: this._dnd.start.pageX, + pageY: this._dnd.start.pageY, + clientX: this._dnd.start.clientX, + clientY: this._dnd.start.clientY, + }, + composed: true, + }) + ) + ) { + return + } + + this._dnd.active = true + this._dnd.child.container.classList.add('dragging') + this._dnd.child.container.style.position = 'fixed' + let rect = this.getBoundingClientRect() + this._dnd.child.container.style.top = rect.top + 'px' + this._dnd.child.container.style.left = rect.left + 'px' + } + + continueDrag() { + if (!this._dnd.active) { + return + } + + let left = + this._dnd.child.master.offsetLeft + + (this._dnd.last.pageX - this._dnd.start.pageX) + let top = + this._dnd.child.master.offsetTop + + (this._dnd.last.pageY - this._dnd.start.pageY) + this._dnd.child.container.style.transform = + 'translate(' + left + 'px, ' + top + 'px)' + + if (this._dnd.moveTimeout === null) { + let delay = Math.max( + 0, + DND_MOVE_THROTTLE - + (this._dnd.last.timeStamp - this._dnd.lastMoveEventTime) + ) + this._dnd.moveTimeout = setTimeout(() => { + this._dnd.moveTimeout = null as any + this._dnd.lastMoveEventTime = this._dnd.last.timeStamp + this.dispatchEvent( + new CustomEvent('drag-unadd-move', { + detail: { + target: this._dnd.child.element, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY, + }, + composed: true, + }) + ) + }, delay) + } + } + + endDrag(event: Event) { + if (this._dnd.active) { + const centerX = + this.left + + this._dnd.center.x + + (this._dnd.last.pageX - this._dnd.start.pageX) + const centerY = + this.top + + this._dnd.center.y + + (this._dnd.last.pageY - this._dnd.start.pageY) + + const elementHeight = this._dnd.child.element.offsetHeight + const elementWidth = this._dnd.child.element.offsetWidth + let dropTarget = this.getChildFromPoint( + this._dnd.last.clientX, + this._dnd.last.clientY + ) + + if ( + this.dispatchEvent( + new CustomEvent('drag-unadd-end', { + cancelable: true, + detail: { + target: this._dnd.child.element, + dropTarget: dropTarget, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY, + centerX, + centerY, + gridHeight: elementHeight, + gridWidth: elementWidth, + }, + composed: true, + }) + ) + ) { + let children = this._children + if (dropTarget && dropTarget !== this._dnd.child.element) { + let dropChild: UnaddContainerChild | null = null + let dropIndex = -1 + let childIndex = -1 + let insertBefore = true + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === this._dnd.child) { + childIndex = i + if (!dropChild) { + insertBefore = false + } + } + + if (children[i].element === dropTarget) { + dropChild = children[i] + dropIndex = i + } + + if (dropIndex >= 0 && childIndex >= 0) { + break + } + } + + if (dropIndex >= 0 && childIndex >= 0) { + // Default action, rearrange the dragged child to before or after + // the child underneath the touch/mouse point. + this.realRemoveChild(this._dnd.child.container) + this.realRemoveChild(this._dnd.child.master) + this.realInsertBefore( + this._dnd.child.container, + insertBefore + ? ((dropChild as UnaddContainerChild).container as any) + : (dropChild as UnaddContainerChild).container.nextSibling + ) + this.realInsertBefore( + this._dnd.child.master, + insertBefore + ? ((dropChild as UnaddContainerChild).master as any) + : (dropChild as UnaddContainerChild).master.nextSibling + ) + children.splice(dropIndex, 0, children.splice(childIndex, 1)[0]) + this.dispatchEvent( + new CustomEvent('drag-unadd-rearrange', {composed: true}) + ) + } + } + } + } else if (this._dnd.timeout !== null) { + let handled = !this.dispatchEvent( + new CustomEvent('unadd-activate', { + cancelable: true, + composed: true, + detail: { + target: this._dnd.child.element, + }, + }) + ) + if (handled) { + event.stopImmediatePropagation() + event.preventDefault() + } + } + + this.cancelDrag() + } + + handleEvent(event: Event) { + switch (event.type) { + case 'touchstart': + case 'mousedown': + event.preventDefault() + if (this._dnd.active || this._dnd.timeout) { + this.cancelDrag() + break + } + + if (event instanceof MouseEvent) { + this._dnd.start.pageX = event.pageX + this._dnd.start.pageY = event.pageY + this._dnd.start.clientX = event.clientX + this._dnd.start.clientY = event.clientY + } else { + this._dnd.start.pageX = (event as TouchEvent).touches[0].pageX + this._dnd.start.pageY = (event as TouchEvent).touches[0].pageY + this._dnd.start.clientX = (event as TouchEvent).touches[0].clientX + this._dnd.start.clientY = (event as TouchEvent).touches[0].clientY + } + + this._dnd.last.pageX = this._dnd.start.pageX + this._dnd.last.pageY = this._dnd.start.pageY + this._dnd.last.clientX = this._dnd.start.clientX + this._dnd.last.clientY = this._dnd.start.clientY + this._dnd.last.timeStamp = event.timeStamp + + let target = event.target as any //gaia-app-icon + + for (; target.parentNode !== this; target = target.parentNode) { + if (target === this || !target.parentNode) { + return + } + } + + // Find the child + let children = this._children + for (let child of children) { + if (child.container === target) { + this._dnd.child = child + break + } + } + + if (!this._dnd.child) { + return + } + + if (this._dnd.delay > 0) { + this._dnd.timeout = setTimeout(() => { + this._dnd.timeout = null as any + this.startDrag() + }, this._dnd.delay) + } else { + this.startDrag() + } + break + + case 'touchmove': + case 'mousemove': + let pageX, pageY, clientX, clientY + if (event instanceof MouseEvent) { + pageX = event.pageX + pageY = event.pageY + clientX = event.clientX + clientY = event.clientY + } else { + pageX = (event as TouchEvent).touches[0].pageX + pageY = (event as TouchEvent).touches[0].pageY + clientX = (event as TouchEvent).touches[0].clientX + clientY = (event as TouchEvent).touches[0].clientY + } + + if (this._dnd.timeout) { + if ( + Math.abs((pageX as number) - this._dnd.start.pageX) > + DND_THRESHOLD || + Math.abs((pageY as number) - this._dnd.start.pageY) > DND_THRESHOLD + ) { + clearTimeout(this._dnd.timeout) + this._dnd.timeout = null as any + } + } else if (this._dnd.active) { + event.preventDefault() + this._dnd.last.pageX = pageX as number + this._dnd.last.pageY = pageY as number + this._dnd.last.clientX = clientX as number + this._dnd.last.clientY = clientY as number + this._dnd.last.timeStamp = event.timeStamp + + this.continueDrag() + } + + if (this._dnd.active) { + const centerX = + this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX) + const centerY = + this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY) + + let distanceX = this._dnd.last.clientX - this._dnd.start.clientX + let distanceY = this._dnd.last.clientY - this._dnd.start.clientY + if (centerY < this.offsetTop) { + this.dispatchEvent( + new CustomEvent('drag-outside-unadd-container', { + detail: { + element: this._dnd.child.element, + centerX, + centerY, + clientX, + clientY, + distanceX, + distanceY, + }, + composed: true, + }) + ) + } + } + break + + case 'touchcancel': + this.cancelDrag() + break + + case 'touchend': + case 'mouseup': + if (this._dnd.active) { + event.preventDefault() + event.stopImmediatePropagation() + } + this.endDrag(event) + break + + case 'click': + if (this._dnd.clickCapture) { + this._dnd.clickCapture = false + event.preventDefault() + event.stopImmediatePropagation() + } + break + + case 'contextmenu': + if (this._dnd.active || this._dnd.timeout) { + event.stopImmediatePropagation() + // event.preventDefault(); + } + break + } + } + + /** + * Temporarily disables element position synchronisation. Useful when adding + * multiple elements to the container at the same time, or in quick + * succession. + */ + freeze() { + this._frozen = true + } + + /** + * Enables element position synchronisation after freeze() has been called. + */ + thaw() { + if (this._frozen) { + this._frozen = false + for (let callback of this._pendingStateChanges) { + callback() + } + this._pendingStateChanges = [] + this.synchronise() + } + } + + /** + * Synchronise positions between the managed container and all children. + * This is called automatically when adding/inserting or removing children, + * but must be called manually if the managed container is manipulated + * outside of these methods (for example, if style properties change, or + * if it's resized). + */ + synchronise() { + if (this._frozen) { + return + } + + let child + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseMaster() + } + } + + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseContainer() + } + } + } + + // 拖拽进未添加开关容器的元素暂放在第一个位置 + dragIn(element: HTMLElement, callback?: Function) { + let child = + element instanceof UnaddContainerChild + ? element + : this.getChildByElement(element) + if (!child) { + this._dnd.child = new UnaddContainerChild(element) + } + + let children = this._children + if (children.length) { + this.realInsertBefore(this._dnd.child.container, children[0].container) + this.realInsertBefore(this._dnd.child.master, children[0].master) + children.splice(0, 0, this._dnd.child) + } else { + this.realAppendChild(this._dnd.child.container) + this.realAppendChild(this._dnd.child.master) + children.push(this._dnd.child) + } + // this.dispatchEvent(new CustomEvent('drag-unadd-rearrange', {composed: true})) + this._dnd.child.container.style.transform = `translate(0px)` + this.synchronise() + if (callback) { + callback() + } + } + + render() { + return html` +
+ +
+ ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'icon-unadd-container': IconUnaddContainer + } +} diff --git a/src/components/icon-unadd-container/index.ts b/src/components/icon-unadd-container/index.ts new file mode 100644 index 0000000..0979672 --- /dev/null +++ b/src/components/icon-unadd-container/index.ts @@ -0,0 +1 @@ +export * from './icon-unadd-container.js' diff --git a/src/components/icon-unadd-container/package.json b/src/components/icon-unadd-container/package.json new file mode 100644 index 0000000..15dd664 --- /dev/null +++ b/src/components/icon-unadd-container/package.json @@ -0,0 +1,22 @@ +{ + "name": "@star-web-components/icon-unadd-container", + "version": "0.0.1", + "description": "", + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": { + "default": "./index.js" + }, + "./index": { + "default": "./index.js" + }, + "./icon-unadd-container.js": { + "default": "./icon-unadd-container.js" + }, + "./package.json": "./package.json" + }, + "author": "", + "license": "ISC" +} diff --git a/src/components/icon-unadd-container/tsconfig.json b/src/components/icon-unadd-container/tsconfig.json new file mode 100644 index 0000000..4ba3e2c --- /dev/null +++ b/src/components/icon-unadd-container/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["*.ts"] +} diff --git a/src/components/icon-unadd-container/unadd-container-child.ts b/src/components/icon-unadd-container/unadd-container-child.ts new file mode 100644 index 0000000..b65950b --- /dev/null +++ b/src/components/icon-unadd-container/unadd-container-child.ts @@ -0,0 +1,114 @@ +export default class UnaddContainerChild { + _element: HTMLElement | null + _container: HTMLElement | null = null + _master: HTMLElement | null = null + _lastElementWidth: number | null = null + _lastElementHeight: number | null = null + _lastElementDisplay: string | null = null + _lastElementOrder: string | null = null + _lastMasterTop: number | null = null + _lastMasterLeft: number | null = null + // 状态计时器 + removed: number | undefined = undefined + added: number | undefined = undefined + + constructor(element: HTMLElement | null) { + this._element = element + this.markDirty() + } + + get element() { + return this._element + } + + /** + * The element that will contain the child element and control its position. + */ + get container() { + if (!this._container) { + // Create container + let container = document.createElement('div') + container.classList.add('gaia-container-child') + container.style.position = 'absolute' + container.style.top = '0' + container.style.left = '0' + container.appendChild(this.element as HTMLElement) //this.element是div.icon-container + + this._container = container + } + return this._container + } + + /** + * The element that will be added to the container that will + * control the element's transform. + */ + get master() { + if (!this._master) { + // Create master + let master = document.createElement('div') + master.style.visibility = 'hidden' + this._master = master + } + return this._master + } + + /** + * Clears any cached style properties. To be used if elements are + * manipulated outside of the methods of this object. + */ + markDirty() { + this._lastElementWidth = null + this._lastElementHeight = null + this._lastElementDisplay = null + this._lastElementOrder = null + this._lastMasterTop = null + this._lastMasterLeft = null + } + + /** + * Synchronise the size of the master with the managed child element. + */ + synchroniseMaster() { + let master = this.master + let element = this.element + + let style = window.getComputedStyle(element as HTMLElement) + let display = style.display + let order = style.order + let width = (element as HTMLElement).offsetWidth + let height = (element as HTMLElement).offsetHeight + + if ( + this._lastElementWidth !== width || + this._lastElementHeight !== height || + this._lastElementDisplay !== display || + this._lastElementOrder !== order + ) { + this._lastElementWidth = width + this._lastElementHeight = height + this._lastElementDisplay = display + this._lastElementOrder = order + + master.style.width = width + 'px' + master.style.height = height + 'px' + master.style.display = display + master.style.order = order + } + } + + /** + * Synchronise the container's transform with the position of the master. + */ + synchroniseContainer() { + let master = this.master + let container = this.container + let top = master.offsetTop + let left = master.offsetLeft + if (this._lastMasterTop !== top || this._lastMasterLeft !== left) { + this._lastMasterTop = top + this._lastMasterLeft = left + container.style.transform = 'translate(' + left + 'px, ' + top + 'px)' + } + } +} diff --git a/src/components/toast/style.ts b/src/components/toast/style.ts new file mode 100644 index 0000000..922866a --- /dev/null +++ b/src/components/toast/style.ts @@ -0,0 +1,92 @@ +import {css} from 'lit' + +export default css` + /** Host + ---------------------------------------------------------*/ + + :host { + position: fixed; + left: 50%; + bottom: 13.33vh; + border-radius: var(--auto-20px); + max-width: 70%; + text-align: center; + z-index: 100; + color: var(--font-main-black, #262626); + overflow: hidden; + transform: translateX(-50%); + } + + /** Inner + ---------------------------------------------------------*/ + + .inner { + display: none; + } + + .inner.visible { + display: block; + } + + /** + ---------------------------------------------------------*/ + + .bread { + max-width: 600px; + margin: 0 auto; + padding: var(--auto-24px) var(--auto-32px); + box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.15); + transform: translateY(100%); + font-size: var(--auto-26px); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--font-normal-color); + + background: var(--base-normal-bgc); + transition: color 0.2s, background 0.2s; + } + + .bread.animate-in { + animation-name: gaia-toast-enter; + animation-fill-mode: forwards; + animation-duration: 300ms; + } + + .bread.animate-out { + animation-name: gaia-toast-leave; + animation-duration: 600ms; + transform: translateY(0%); + } + + @keyframes gaia-toast-enter { + 0% { + transform: translateY(100%); + opacity: 0; + } + + 40% { + opacity: 0; + } + + 100% { + transform: translateY(0%); + opacity: 1; + } + } + + @keyframes gaia-toast-leave { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + + @media screen and (orientation: landscape) { + :host { + } + } +` diff --git a/src/components/toast/toast.ts b/src/components/toast/toast.ts index b2b8170..4aae9e4 100644 --- a/src/components/toast/toast.ts +++ b/src/components/toast/toast.ts @@ -1,6 +1,9 @@ -import {html, LitElement, CSSResultArray, TemplateResult} from 'lit' +import {html, CSSResultArray} from 'lit' import {customElement, property, query, state} from 'lit/decorators.js' import {sharedStyles} from './toast-styles.js' +import {autoPxStyle} from '../base/auto-px-style' +import {StarBaseElement} from '../base/star-base-element.js' +import style from './style' export const toastVariants: ToastVariants[] = [ 'negative', @@ -18,17 +21,110 @@ export type ToastVariants = | 'warning' | '' +enum Direction { + top, + bottom, +} + +interface Options { + [prop: string]: string | boolean | undefined + direction?: keyof typeof Direction + icon?: boolean + close?: boolean + verline?: boolean + do?: boolean + information: string +} +// BUG: 该组件存在 UI 隐患, 因为弹出键盘时, 使用该组件的应用将会被键盘挤压窗口 +// 导致该组件位置会被推至屏幕上方, 无法避免; 即便不被挤压(如Homescreen), 仍会 +// 存在被键盘遮挡的情况, 依旧无法避免 @customElement('star-toast') -export class StarToast extends LitElement { +export class StarToast extends StarBaseElement { public static override get styles(): CSSResultArray { - return [sharedStyles] + return [style, sharedStyles, autoPxStyle] } + /* 暂时更改使用: start */ + constructor(options: Options) { + super() + Object.assign(this, options) + document.body.appendChild(this) + this.appended = true + } + + @property() + public direction: keyof typeof Direction = 'bottom' + + @property({type: Number}) + get timeout() { + return Number(this.getAttribute('timeout')) || 3000 + } + + set timeout(value) { + let current = Number(this.getAttribute('timeout')) + + if (current == value) { + return + } else if (!value) { + this.removeAttribute('timeout') + } else { + this.setAttribute('timeout', String(value)) + } + } + + appended: boolean = false + + onAnimateOutEnd = (_: any) => {} + + hideTimeout?: number + + @query('.inner') inner!: HTMLElement + @query('.bread') bread!: HTMLElement + + show = () => { + if (!this.appended) { + document.body.appendChild(this) + this.appended = true + } + this.open = true + this.bread.removeEventListener('animationend', this.onAnimateOutEnd) + clearTimeout(this.hideTimeout) + + this.inner.classList.add('visible') + this.bread.classList.remove('animate-out') + this.bread.classList.add('animate-in') + this.hideTimeout = window.setTimeout(this.hide.bind(this), this.timeout) + } + + hide = () => { + clearTimeout(this.hideTimeout) + this.open = false + this.bread.classList.remove('animate-in') + this.bread.classList.add('animate-out') + + this.bread.removeEventListener('animationend', this.onAnimateOutEnd) + this.onAnimateOutEnd = () => { + this.bread.removeEventListener('animationend', this.onAnimateOutEnd) + this.bread.classList.remove('animate-out') + this.inner.classList.remove('visible') + } + + this.bread.addEventListener('animationend', this.onAnimateOutEnd) + } + + render() { + return html` +
+
${this.information}
+
+ ` + } + /* 暂时更改使用: end */ @state() public open: Boolean = false - @property({type: Number}) - private timeout = 5000 + // @property({type: Number}) + // private timeout = 5000 @property({type: String}) public variant: ToastVariants = '' @@ -52,77 +148,77 @@ export class StarToast extends LitElement { @property({type: String}) public buttonName = '' - private renderIcon(): TemplateResult { - switch (this.variant) { - case 'positive': - return html` - - - - ` - case 'warning': - case 'info': - return html` - - - - ` - case 'error': - case 'negative': - default: - return html` - - - - ` - } - } + // private renderIcon() { + // switch (this.variant) { + // case 'positive': + // return html` + // + // + // + // ` + // case 'warning': + // case 'info': + // return html` + // + // + // + // ` + // case 'error': + // case 'negative': + // default: + // return html` + // + // + // + // ` + // } + // } - protected override render(): TemplateResult { - return html` -
-
- ${this.renderIcon()} -
-
- ${this.information} - Do Something -
- - X -
-
- ${this.buttonName} -
- ` - } + // protected override render() { + // return html` + //
+ //
+ // ${this.renderIcon()} + //
+ //
+ // ${this.information} + // Do Something + //
+ // + // X + //
+ //
+ // ${this.buttonName} + //
+ // ` + // } @query('#toast') toast!: HTMLElement diff --git a/src/components/toast/tsconfig.json b/src/components/toast/tsconfig.json index 4ba3e2c..9198057 100644 --- a/src/components/toast/tsconfig.json +++ b/src/components/toast/tsconfig.json @@ -4,5 +4,5 @@ "composite": true, "rootDir": "../../" }, - "include": ["*.ts"] + "include": ["*.ts", "../base/*.ts"] } diff --git a/src/test/panels/battery-square/battery-square.ts b/src/test/panels/battery-square/battery-square.ts index 20341c8..b39b4d8 100644 --- a/src/test/panels/battery-square/battery-square.ts +++ b/src/test/panels/battery-square/battery-square.ts @@ -1,4 +1,4 @@ -import {html, LitElement, CSSResultArray} from 'lit' +import {html, LitElement, CSSResultArray, css} from 'lit' import {customElement} from 'lit/decorators.js' import {sharedStyles} from '../../../components/battery-square/battery-square-styles' @@ -6,15 +6,26 @@ import {sharedStyles} from '../../../components/battery-square/battery-square-st export class PanelBatterysquare extends LitElement { render() { return html` - - - - +
+ +
` } public static override get styles(): CSSResultArray { - return [sharedStyles] + return [ + sharedStyles, + css` + :host { + height: 100vh; + width: 100vw; + } + #container { + height: 31.01vh; + width: 26.145vw; + } + `, + ] } } diff --git a/src/test/panels/battery/battery.ts b/src/test/panels/battery/battery.ts index f02dae4..9151bb0 100644 --- a/src/test/panels/battery/battery.ts +++ b/src/test/panels/battery/battery.ts @@ -8,9 +8,9 @@ export class PanelBattery extends LitElement { @query('star-battery') battery!: HTMLElement render() { return html` - - - +
+ +
` } @@ -29,8 +29,8 @@ export class PanelBattery extends LitElement { width: 100vw; } #container { - height: 100vh; - width: 100vw; + height: 60vh; + width: 50vw; } `, ] diff --git a/src/test/panels/iconfont/REMEAD.md b/src/test/panels/iconfont/REMEAD.md new file mode 100644 index 0000000..04b8260 --- /dev/null +++ b/src/test/panels/iconfont/REMEAD.md @@ -0,0 +1,36 @@ +# iconfont字体库 +## 介绍 +iconfont字体库支持多色图标字体,可通过Unicode、fontclass和symbol三种方式进行引用。 +## iconfont使用方法 +> Unicode 引用 +``` +第一步:设置@font-face + +@font-face { + font-family: "iconfont-signal"; + src: + url('iconfont-signal.ttf?t=1667956429504') format('truetype'); +} + +第二步:定义样式,如 + +.iconfont { + font-family: "iconfont-signal" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +第三步:挑选相应图标并获取字体编码,应用于页面(字体编码可在iconfont.json文件中查询) + +3 + +> Symbol使用方式 +在需要图标处通过xlink方式链接到ffont.symbol.svg,以‘#ffont’+图表名称 的方式调用对应图标 +``` + + +``` \ No newline at end of file diff --git a/src/test/panels/iconfont/ffont.symbol.svg b/src/test/panels/iconfont/ffont.symbol.svg new file mode 100644 index 0000000..f646ff8 --- /dev/null +++ b/src/test/panels/iconfont/ffont.symbol.svg @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/panels/iconfont/iconfont-style.ts b/src/test/panels/iconfont/iconfont-style.ts new file mode 100644 index 0000000..e09310c --- /dev/null +++ b/src/test/panels/iconfont/iconfont-style.ts @@ -0,0 +1,48 @@ +import {css, CSSResult} from 'lit' +import './iconfont/iconfont-signal.css' +import './iconfont/iconfont-wifi.css' + +export const iconFontStyles: CSSResult = css` + @font-face { + font-family: "iconfont-signal"; /* Project id 3582390 */ + /* Color fonts */ + src: + url('iconfont.ttf?t=1667956429504') format('truetype'); + } + @font-face { + font-family: "iconfont-wifi"; /* Project id 3582390 */ + /* Color fonts */ + src: + url('iconfont.ttf?t=1667957389350') format('truetype'); + } + .iconfont-signal { + font-family: "iconfont-signal" !important; + font-size: 32px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: inline-block; + width: 40px; + height: 40px; + margin: 0 20px; + } + .iconfont-wifi { + font-family: "iconfont-wifi" !important; + font-size: 32px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: inline-block; + width: 40px; + height: 40px; + margin: 0 20px; + } + .icon { + display: inline-block; + width: 40px; + height: 40px; + } + h3 { + color: red; + } +` diff --git a/src/test/panels/iconfont/iconfont.ts b/src/test/panels/iconfont/iconfont.ts new file mode 100644 index 0000000..0631231 --- /dev/null +++ b/src/test/panels/iconfont/iconfont.ts @@ -0,0 +1,553 @@ +import { + html, + LitElement, + CSSResultArray, +} from 'lit' +import {customElement} from 'lit/decorators.js' +import {iconFontStyles} from './iconfont-style' + +@customElement('panel-iconfont') +export class PanelIconFont extends LitElement { + public icondata_signal = { + "id": "3582390", + "name": "多色-signal", + "font_family": "iconfont-signal", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "32701896", + "name": "flow-none", + "font_class": "flow-none", + "unicode": "e664", + "unicode_decimal": 58980 + }, + { + "icon_id": "32701897", + "name": "flow-down", + "font_class": "flow-down", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "32701898", + "name": "flow-up", + "font_class": "flow-up", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "32701899", + "name": "flow-updown", + "font_class": "flow-updown", + "unicode": "e667", + "unicode_decimal": 58983 + }, + { + "icon_id": "32701900", + "name": "signal-0", + "font_class": "signal-0", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "32701901", + "name": "signal-1", + "font_class": "signal-1", + "unicode": "e669", + "unicode_decimal": 58985 + }, + { + "icon_id": "32701902", + "name": "signal-2", + "font_class": "signal-2", + "unicode": "e66a", + "unicode_decimal": 58986 + }, + { + "icon_id": "32701903", + "name": "signal-2g-0", + "font_class": "signal-2g-0", + "unicode": "e66b", + "unicode_decimal": 58987 + }, + { + "icon_id": "32701904", + "name": "signal-2g-1", + "font_class": "signal-2g-1", + "unicode": "e66c", + "unicode_decimal": 58988 + }, + { + "icon_id": "32701905", + "name": "signal-2g-3", + "font_class": "signal-2g-3", + "unicode": "e66d", + "unicode_decimal": 58989 + }, + { + "icon_id": "32701906", + "name": "signal-2g-2", + "font_class": "signal-2g-2", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "32701907", + "name": "signal-2g-4", + "font_class": "signal-2g-4", + "unicode": "e66f", + "unicode_decimal": 58991 + }, + { + "icon_id": "32701908", + "name": "signal-3", + "font_class": "signal-3", + "unicode": "e670", + "unicode_decimal": 58992 + }, + { + "icon_id": "32701909", + "name": "signal-3g-0", + "font_class": "signal-3g-0", + "unicode": "e671", + "unicode_decimal": 58993 + }, + { + "icon_id": "32701910", + "name": "signal-3g-1", + "font_class": "signal-3g-1", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "32701911", + "name": "signal-3g-2", + "font_class": "signal-3g-2", + "unicode": "e673", + "unicode_decimal": 58995 + }, + { + "icon_id": "32701912", + "name": "signal-3g-3", + "font_class": "signal-3g-3", + "unicode": "e674", + "unicode_decimal": 58996 + }, + { + "icon_id": "32701913", + "name": "signal-4g-0", + "font_class": "signal-4g-0", + "unicode": "e675", + "unicode_decimal": 58997 + }, + { + "icon_id": "32701914", + "name": "signal-3g-4", + "font_class": "signal-3g-4", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "32701915", + "name": "signal-4g-1", + "font_class": "signal-4g-1", + "unicode": "e677", + "unicode_decimal": 58999 + }, + { + "icon_id": "32701916", + "name": "signal-4", + "font_class": "signal-4", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "32701917", + "name": "signal-4g-2", + "font_class": "signal-4g-2", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "32701918", + "name": "signal-5g-0", + "font_class": "signal-5g-0", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "32701919", + "name": "signal-5g-2", + "font_class": "signal-5g-2", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "32701920", + "name": "signal-4g-4", + "font_class": "signal-4g-4", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "32701921", + "name": "signal-5g-1", + "font_class": "signal-5g-1", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "32701922", + "name": "signal-4g-3", + "font_class": "signal-4g-3", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "32701923", + "name": "signal-5g-3", + "font_class": "signal-5g-3", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "32701924", + "name": "signal-5g-4", + "font_class": "signal-5g-4", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "32701925", + "name": "signal-e-1", + "font_class": "signal-e-1", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "32701926", + "name": "signal-e-0", + "font_class": "signal-e-0", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "32701927", + "name": "signal-e-3", + "font_class": "signal-e-3", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "32701928", + "name": "signal-roaming", + "font_class": "signal-roaming", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "32701929", + "name": "signal-e-4", + "font_class": "signal-e-4", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "32701930", + "name": "signal-open-card", + "font_class": "signal-open-card", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "32701932", + "name": "signal-e-2", + "font_class": "signal-e-2", + "unicode": "e688", + "unicode_decimal": 59016 + } + ] + } + + public icondata_wifi = { + "id": "3756669", + "name": "多色-wifi", + "font_family": "iconfont-wifi", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "32723522", + "name": "wifi-1", + "font_class": "wifi-1", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "32723523", + "name": "wifi-0", + "font_class": "wifi-0", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "32723524", + "name": "wifi-2", + "font_class": "wifi-2", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "32723525", + "name": "wifi-lock-1", + "font_class": "wifi-lock-1", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "32723526", + "name": "wifi-lock-2", + "font_class": "wifi-lock-2", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "32723527", + "name": "wifi-3", + "font_class": "wifi-3", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "32723528", + "name": "wifi-lock-3", + "font_class": "wifi-lock-3", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "32723529", + "name": "wifi-nonet-0", + "font_class": "wifi-nonet-0", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "32723530", + "name": "wifi-lock-0", + "font_class": "wifi-lock-0", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "32723531", + "name": "wifi-nonet-1", + "font_class": "wifi-nonet-1", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "32723532", + "name": "wifi-lock-4", + "font_class": "wifi-lock-4", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "32723533", + "name": "wifi-permissions", + "font_class": "wifi-permissions", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "32723534", + "name": "wifi-nonet-2", + "font_class": "wifi-nonet-2", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "32723535", + "name": "wifi-nonet-3", + "font_class": "wifi-nonet-3", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "32723615", + "name": "wifi-4", + "font_class": "wifi-4", + "unicode": "e687", + "unicode_decimal": 59015 + } + ] + } + + initSignalIcon() { + let html = '' + html += '

signal字体库iconfont-signal

' + for (let i = 0; i < this.icondata_signal.glyphs.length; i++) { + html += + '&#x' + + this.icondata_signal.glyphs[i].unicode + + ';' + html += + '' + + this.icondata_signal.glyphs[i].name + + '
' + } + let node = document.createElement('div') + node.className = 'p-signal' + node.innerHTML = html + this.shadowRoot!.querySelector('.abc')!.appendChild(node) + } + + initWifiIcon() { + let html = '' + html += '

WIFI字体库iconfont-wifi

' + for (let i = 0; i < this.icondata_wifi.glyphs.length; i++) { + html += + '&#x' + + this.icondata_wifi.glyphs[i].unicode + + ';' + html += + '' + + this.icondata_wifi.glyphs[i].name + + '
' + } + let node = document.createElement('div') + node.className = 'p-wifi' + node.innerHTML = html + this.shadowRoot!.querySelector('.abc')!.appendChild(node) + } + + firstUpdated() { + this.initSignalIcon() + this.initWifiIcon() + } + + render() { + return html` +
+
+

使用ffont.svg的symbol方法展示

+
  • + +

    ffont-QQ

    +
  • +
  • + +

    ffont-wifi-nonet-1

    +
  • +
  • + +

    ffont-wifi-nonet-2

    +
  • +
  • + +

    ffont-wifi-nonet-3

    +
  • +
  • + +

    ffont-wifi-1

    +
  • +
  • + +

    ffont-wifi-2

    +
  • +
  • + +

    ffont-wifi-3

    +
  • +
  • + +

    ffont-wifi-4

    +
  • +
  • + +

    ffont-signal-0

    +
  • +
  • + +

    ffont-signal-1

    +
  • +
  • + +

    ffont-signal-2

    +
  • +
  • + +

    ffont-signal-3

    +
  • +
  • + +

    ffont-signal-4

    +
  • +
    + ` + } + + public static override get styles(): CSSResultArray { + return [iconFontStyles] + } +} + +declare global { + interface HTMLElementTagNameMap { + 'panel-iconfont': PanelIconFont + } +} diff --git a/src/test/panels/iconfont/iconfont/iconfont-signal.css b/src/test/panels/iconfont/iconfont/iconfont-signal.css new file mode 100644 index 0000000..c98fa96 --- /dev/null +++ b/src/test/panels/iconfont/iconfont/iconfont-signal.css @@ -0,0 +1,159 @@ +@font-face { + font-family: "iconfont-signal"; /* Project id 3582390 */ + /* Color fonts */ + src: + url('iconfont-signal.ttf?t=1667956429504') format('truetype'); +} + +.iconfont-signal { + font-family: "iconfont-signal" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-flow-none:before { + content: "\e664"; +} + +.icon-flow-down:before { + content: "\e665"; +} + +.icon-flow-up:before { + content: "\e666"; +} + +.icon-flow-updown:before { + content: "\e667"; +} + +.icon-signal-0:before { + content: "\e668"; +} + +.icon-signal-1:before { + content: "\e669"; +} + +.icon-signal-2:before { + content: "\e66a"; +} + +.icon-signal-2g-0:before { + content: "\e66b"; +} + +.icon-signal-2g-1:before { + content: "\e66c"; +} + +.icon-signal-2g-3:before { + content: "\e66d"; +} + +.icon-signal-2g-2:before { + content: "\e66e"; +} + +.icon-signal-2g-4:before { + content: "\e66f"; +} + +.icon-signal-3:before { + content: "\e670"; +} + +.icon-signal-3g-0:before { + content: "\e671"; +} + +.icon-signal-3g-1:before { + content: "\e672"; +} + +.icon-signal-3g-2:before { + content: "\e673"; +} + +.icon-signal-3g-3:before { + content: "\e674"; +} + +.icon-signal-4g-0:before { + content: "\e675"; +} + +.icon-signal-3g-4:before { + content: "\e676"; +} + +.icon-signal-4g-1:before { + content: "\e677"; +} + +.icon-signal-4:before { + content: "\e678"; +} + +.icon-signal-4g-2:before { + content: "\e679"; +} + +.icon-signal-5g-0:before { + content: "\e67a"; +} + +.icon-signal-5g-2:before { + content: "\e67b"; +} + +.icon-signal-4g-4:before { + content: "\e67c"; +} + +.icon-signal-5g-1:before { + content: "\e67d"; +} + +.icon-signal-4g-3:before { + content: "\e67e"; +} + +.icon-signal-5g-3:before { + content: "\e67f"; +} + +.icon-signal-5g-4:before { + content: "\e680"; +} + +.icon-signal-e-1:before { + content: "\e681"; +} + +.icon-signal-e-0:before { + content: "\e682"; +} + +.icon-signal-e-3:before { + content: "\e683"; +} + +.icon-signal-roaming:before { + content: "\e684"; +} + +.icon-signal-e-4:before { + content: "\e685"; +} + +.icon-signal-open-card:before { + content: "\e686"; +} + +.icon-signal-e-2:before { + content: "\e688"; +} + diff --git a/src/test/panels/iconfont/iconfont/iconfont-signal.json b/src/test/panels/iconfont/iconfont/iconfont-signal.json new file mode 100644 index 0000000..db67ac2 --- /dev/null +++ b/src/test/panels/iconfont/iconfont/iconfont-signal.json @@ -0,0 +1,261 @@ +{ + "id": "3582390", + "name": "多色-signal", + "font_family": "iconfont-signal", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "32701896", + "name": "flow-none", + "font_class": "flow-none", + "unicode": "e664", + "unicode_decimal": 58980 + }, + { + "icon_id": "32701897", + "name": "flow-down", + "font_class": "flow-down", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "32701898", + "name": "flow-up", + "font_class": "flow-up", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "32701899", + "name": "flow-updown", + "font_class": "flow-updown", + "unicode": "e667", + "unicode_decimal": 58983 + }, + { + "icon_id": "32701900", + "name": "signal-0", + "font_class": "signal-0", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "32701901", + "name": "signal-1", + "font_class": "signal-1", + "unicode": "e669", + "unicode_decimal": 58985 + }, + { + "icon_id": "32701902", + "name": "signal-2", + "font_class": "signal-2", + "unicode": "e66a", + "unicode_decimal": 58986 + }, + { + "icon_id": "32701903", + "name": "signal-2g-0", + "font_class": "signal-2g-0", + "unicode": "e66b", + "unicode_decimal": 58987 + }, + { + "icon_id": "32701904", + "name": "signal-2g-1", + "font_class": "signal-2g-1", + "unicode": "e66c", + "unicode_decimal": 58988 + }, + { + "icon_id": "32701905", + "name": "signal-2g-3", + "font_class": "signal-2g-3", + "unicode": "e66d", + "unicode_decimal": 58989 + }, + { + "icon_id": "32701906", + "name": "signal-2g-2", + "font_class": "signal-2g-2", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "32701907", + "name": "signal-2g-4", + "font_class": "signal-2g-4", + "unicode": "e66f", + "unicode_decimal": 58991 + }, + { + "icon_id": "32701908", + "name": "signal-3", + "font_class": "signal-3", + "unicode": "e670", + "unicode_decimal": 58992 + }, + { + "icon_id": "32701909", + "name": "signal-3g-0", + "font_class": "signal-3g-0", + "unicode": "e671", + "unicode_decimal": 58993 + }, + { + "icon_id": "32701910", + "name": "signal-3g-1", + "font_class": "signal-3g-1", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "32701911", + "name": "signal-3g-2", + "font_class": "signal-3g-2", + "unicode": "e673", + "unicode_decimal": 58995 + }, + { + "icon_id": "32701912", + "name": "signal-3g-3", + "font_class": "signal-3g-3", + "unicode": "e674", + "unicode_decimal": 58996 + }, + { + "icon_id": "32701913", + "name": "signal-4g-0", + "font_class": "signal-4g-0", + "unicode": "e675", + "unicode_decimal": 58997 + }, + { + "icon_id": "32701914", + "name": "signal-3g-4", + "font_class": "signal-3g-4", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "32701915", + "name": "signal-4g-1", + "font_class": "signal-4g-1", + "unicode": "e677", + "unicode_decimal": 58999 + }, + { + "icon_id": "32701916", + "name": "signal-4", + "font_class": "signal-4", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "32701917", + "name": "signal-4g-2", + "font_class": "signal-4g-2", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "32701918", + "name": "signal-5g-0", + "font_class": "signal-5g-0", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "32701919", + "name": "signal-5g-2", + "font_class": "signal-5g-2", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "32701920", + "name": "signal-4g-4", + "font_class": "signal-4g-4", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "32701921", + "name": "signal-5g-1", + "font_class": "signal-5g-1", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "32701922", + "name": "signal-4g-3", + "font_class": "signal-4g-3", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "32701923", + "name": "signal-5g-3", + "font_class": "signal-5g-3", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "32701924", + "name": "signal-5g-4", + "font_class": "signal-5g-4", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "32701925", + "name": "signal-e-1", + "font_class": "signal-e-1", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "32701926", + "name": "signal-e-0", + "font_class": "signal-e-0", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "32701927", + "name": "signal-e-3", + "font_class": "signal-e-3", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "32701928", + "name": "signal-roaming", + "font_class": "signal-roaming", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "32701929", + "name": "signal-e-4", + "font_class": "signal-e-4", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "32701930", + "name": "signal-open-card", + "font_class": "signal-open-card", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "32701932", + "name": "signal-e-2", + "font_class": "signal-e-2", + "unicode": "e688", + "unicode_decimal": 59016 + } + ] +} diff --git a/src/test/panels/iconfont/iconfont/iconfont-signal.ttf b/src/test/panels/iconfont/iconfont/iconfont-signal.ttf new file mode 100644 index 0000000..f558a47 Binary files /dev/null and b/src/test/panels/iconfont/iconfont/iconfont-signal.ttf differ diff --git a/src/test/panels/iconfont/iconfont/iconfont-wifi.css b/src/test/panels/iconfont/iconfont/iconfont-wifi.css new file mode 100644 index 0000000..b80e0f5 --- /dev/null +++ b/src/test/panels/iconfont/iconfont/iconfont-wifi.css @@ -0,0 +1,75 @@ +@font-face { + font-family: "iconfont-wifi"; /* Project id 3756669 */ + /* Color fonts */ + src: + url('iconfont-wifi.ttf?t=1667957389350') format('truetype'); +} + +.iconfont-wifi { + font-family: "iconfont-wifi" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-wifi-1:before { + content: "\e679"; +} + +.icon-wifi-0:before { + content: "\e67a"; +} + +.icon-wifi-2:before { + content: "\e67b"; +} + +.icon-wifi-lock-1:before { + content: "\e67c"; +} + +.icon-wifi-lock-2:before { + content: "\e67d"; +} + +.icon-wifi-3:before { + content: "\e67e"; +} + +.icon-wifi-lock-3:before { + content: "\e67f"; +} + +.icon-wifi-nonet-0:before { + content: "\e680"; +} + +.icon-wifi-lock-0:before { + content: "\e681"; +} + +.icon-wifi-nonet-1:before { + content: "\e682"; +} + +.icon-wifi-lock-4:before { + content: "\e683"; +} + +.icon-wifi-permissions:before { + content: "\e684"; +} + +.icon-wifi-nonet-2:before { + content: "\e685"; +} + +.icon-wifi-nonet-3:before { + content: "\e686"; +} + +.icon-wifi-4:before { + content: "\e687"; +} + diff --git a/src/test/panels/iconfont/iconfont/iconfont-wifi.json b/src/test/panels/iconfont/iconfont/iconfont-wifi.json new file mode 100644 index 0000000..9b15959 --- /dev/null +++ b/src/test/panels/iconfont/iconfont/iconfont-wifi.json @@ -0,0 +1,114 @@ +{ + "id": "3756669", + "name": "多色-wifi", + "font_family": "iconfont-wifi", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "32723522", + "name": "wifi-1", + "font_class": "wifi-1", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "32723523", + "name": "wifi-0", + "font_class": "wifi-0", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "32723524", + "name": "wifi-2", + "font_class": "wifi-2", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "32723525", + "name": "wifi-lock-1", + "font_class": "wifi-lock-1", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "32723526", + "name": "wifi-lock-2", + "font_class": "wifi-lock-2", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "32723527", + "name": "wifi-3", + "font_class": "wifi-3", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "32723528", + "name": "wifi-lock-3", + "font_class": "wifi-lock-3", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "32723529", + "name": "wifi-nonet-0", + "font_class": "wifi-nonet-0", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "32723530", + "name": "wifi-lock-0", + "font_class": "wifi-lock-0", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "32723531", + "name": "wifi-nonet-1", + "font_class": "wifi-nonet-1", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "32723532", + "name": "wifi-lock-4", + "font_class": "wifi-lock-4", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "32723533", + "name": "wifi-permissions", + "font_class": "wifi-permissions", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "32723534", + "name": "wifi-nonet-2", + "font_class": "wifi-nonet-2", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "32723535", + "name": "wifi-nonet-3", + "font_class": "wifi-nonet-3", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "32723615", + "name": "wifi-4", + "font_class": "wifi-4", + "unicode": "e687", + "unicode_decimal": 59015 + } + ] +} diff --git a/src/test/panels/iconfont/iconfont/iconfont-wifi.ttf b/src/test/panels/iconfont/iconfont/iconfont-wifi.ttf new file mode 100644 index 0000000..17111f5 Binary files /dev/null and b/src/test/panels/iconfont/iconfont/iconfont-wifi.ttf differ diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 9742a6b..4646cf4 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -38,6 +38,7 @@ import './slider/slider' import './switch/switch' import './toast/toast' import './weather/weather' +import './iconfont/iconfont' import './swiper/swiper' import './animation/animation' @@ -303,7 +304,7 @@ export class PanelRoot extends LitElement { iconcolor="blue" href="#battery" > -
    + +
    + + diff --git a/src/test/panels/shared-picker-styles.ts b/src/test/panels/shared-picker-styles.ts index 941dd10..496bbe7 100644 --- a/src/test/panels/shared-picker-styles.ts +++ b/src/test/panels/shared-picker-styles.ts @@ -1,4 +1,5 @@ import {css, CSSResult} from 'lit' +import './iconfont/iconfont/iconfont.css' export const sharedPickerStyles: CSSResult = css` * { diff --git a/src/test/panels/toast/toast.ts b/src/test/panels/toast/toast.ts index e0d63b1..0ee830c 100644 --- a/src/test/panels/toast/toast.ts +++ b/src/test/panels/toast/toast.ts @@ -1,13 +1,37 @@ import {html, LitElement, CSSResultArray} from 'lit' import {customElement} from 'lit/decorators.js' import {sharedStyles} from '../../../components/toast/toast-styles' +import '../../../components/button' +import {StarToast} from '../../../components/toast' @customElement('panel-toast') export class PanelToast extends LitElement { + toast: StarToast = new StarToast({information: 'test'}) + constructor() { + super() + } public static override get styles(): CSSResultArray { return [sharedStyles] } + + show() { + if (this.toast.open) { + this.toast.hide() + } else { + this.toast.show() + } + } render() { + return html` + + ` + } + render_bak() { return html`

    普通的