diff --git a/src/components/base/global-style.ts b/src/components/base/global-style.ts index ad5877f..2717357 100644 --- a/src/components/base/global-style.ts +++ b/src/components/base/global-style.ts @@ -456,6 +456,7 @@ const baseComponentStyle = css` --li-label: var(--font-main-black); --li-description: var(--font-sec-auxiliary-black); --li-square: #B3B3B3; + --li-value-warn: var(--theme-red); --li-value-primary: var(--theme-blue); --li-value-default: var(--font-sec-auxiliary-black); --li-link: var(--linear-icon32-black); @@ -468,6 +469,9 @@ const baseComponentStyle = css` --card-label: var(--font-main-black); --card-link: var(--linear-icon32-black); + /* Selector */ + --selector-icon-color: var(--linear-icon32-black); + /* Radio */ --bor-radio-off: var(--auto-3px) solid rgba(38, 38, 38, 0.25); --bor-radio-off: var(--auto-3px) solid var(--opacity-white-25); diff --git a/src/components/button-group/README.md b/src/components/button-group/README.md index 8bebb1c..c8d9ff6 100644 --- a/src/components/button-group/README.md +++ b/src/components/button-group/README.md @@ -10,6 +10,7 @@ | -------------- | ------- | -------- | -------------------------- | | vertical | Boolean | false | 用于指示是否显示垂直按钮组 | | split | Boolean | false | 用于指示是否显示分隔符 | +| inheritRadius | Boolean | false | 用于指示是否继承倒角 |
star-button-group组件支持的插槽
@@ -38,6 +39,11 @@ button-group 将 100% 填充父容器所给的空间,再均匀平铺\ + + + + + ``` ## 注意 diff --git a/src/components/card/card.css.ts b/src/components/card/card.css.ts index 307d02b..a1040c0 100644 --- a/src/components/card/card.css.ts +++ b/src/components/card/card.css.ts @@ -4,6 +4,7 @@ export default css` :host { --background-image-url: ''; flex: 1; + display: block; } div { diff --git a/src/components/card/card.ts b/src/components/card/card.ts index 9830d25..43cebfe 100644 --- a/src/components/card/card.ts +++ b/src/components/card/card.ts @@ -42,6 +42,9 @@ export class StarCard extends StarBaseElement { */ @property({type: Boolean, reflect: true}) checked!: boolean + /* 该value可供在扩展的 radio-group 中使用 */ + @property({type: String, reflect: true}) value!: string + @property({type: String}) type = 'base' // @property({type: String}) size = "medium" @property({type: String}) heading = '' diff --git a/src/components/dialog/README.md b/src/components/dialog/README.md index bb91160..9fd8cc1 100644 --- a/src/components/dialog/README.md +++ b/src/components/dialog/README.md @@ -14,17 +14,48 @@ 基类采用的三段式结构: ``` -------------------- -| 图标/标题 | -------------------- -| 内容 | -| 纯文本/富文本/表单 | -------------------- -| 按钮/按钮组 | -------------------- +┌─────────────────────────────────────────────────┐ +| Header | +├─────────────────────────────────────────────────| +| ┌─────────────────────────────────────────────┐ | +| │ Icon(star-icons/svg-icon/png...) │ | +| └─────────────────────────────────────────────┘ | +| ┌─────────────────────────────────────────────┐ | +| │ Title(alert/confirm/prompt/select/Custom... │ | +| └─────────────────────────────────────────────┘ | +└─────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +| Content | +├─────────────────────────────────────────────────| +│ ┌──────┐ ┌───────┐ │ +│ │ Text │ │ Image │ │ +│ └──────┘ └───────┘ │ +│ ┌──────────────────────────────────────┐ │ +│ │ Form │ │ +│ │ ┌─────────────────┐ ┌──────────────┐ │ │ +│ │ │ Input(Password) │ │ ActionButton │ │ │ +│ │ └─────────────────┘ └──────────────┘ │ │ +│ └──────────────────────────────────────┘ │ +│ ┌────────────────────────────────┐ │ +│ │ Select (Multiple)(Group)(Size) │ │ +│ │ ┌────────────┐ │ │ +│ │ │ RadioGroup │ │ │ +│ │ └────────────┘ │ │ +│ └────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +| Footer | +├─────────────────────────────────────────────────| +│ ┌─────────────────────────────────────────────┐ │ +│ │ StarButtonGroup │ │ +│ │ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ StarButton │ │ StarButton │ ... │ │ +│ │ └────────────┘ └────────────┘ │ │ +│ └─────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ ``` -## 系统弹窗(alert/confirm/prompt) +## 系统模态弹窗(alert/confirm/prompt) ``` alert @@ -53,6 +84,112 @@ prompt ------------------- ``` +## 系统用户代理弹窗(select) + +### 值选择器 + +对应使用的原生标签: + +```html + + + + + + + + + + + +``` + +### 时间日期选择器 + +```html + + + + + + + +``` + +### UI 设计内容 + +触发形态: + +``` + +``` + +``` +┌───────────┐ +| Header | +├───────────| +| ┌───────┐ | +| │ Title │ | +| └───────┘ | +└───────────┘ +┌─────────────────────────────────────────────────┐ +| Content | +├─────────────────────────────────────────────────| +│ ┌─single select──────────────────┐ │ +│ │ │ │ +│ │ ┌────────────┐ │ │ +│ │ │ RadioGroup │ │ │ +│ │ └────────────┘ │ │ +│ └────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ +┌───────────────────────────────────┐ +| Footer | +├───────────────────────────────────| +│ ┌───────────────────────────────┐ │ +│ │ StarButtonGroup │ │ +│ ├─────────single select─────────| │ +│ │ ┌────────────┐ │ │ +│ │ │ StarButton │ │ │ +│ │ └────────────┘ │ │ +│ ├───────multiple select─────────| │ +│ │ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ StarButton │ │ StarButton │ │ │ +│ │ └────────────┘ └────────────┘ │ │ +│ └───────────────────────────────┘ │ +└───────────────────────────────────┘ +``` + +### 转换 + ## TBD: 使用用户代理支持的 使用浏览器用户代理支持的时,可以省去自行设定模态的步骤,并且其自身已经拥有了默认排版,省去设置样式的行为。 diff --git a/src/components/dialog/index.ts b/src/components/dialog/index.ts index 63744e1..21b250c 100644 --- a/src/components/dialog/index.ts +++ b/src/components/dialog/index.ts @@ -1,3 +1,4 @@ export {StarAlertDialog} from './alert-dialog.js' export {StarConfirmDialog} from './confirm-dialog.js' export {StarPromptDialog} from './prompt-dialog.js' +export {StarSelectDialog} from './select-dialog.js' diff --git a/src/components/dialog/select-dialog.css.ts b/src/components/dialog/select-dialog.css.ts new file mode 100644 index 0000000..8153925 --- /dev/null +++ b/src/components/dialog/select-dialog.css.ts @@ -0,0 +1,32 @@ +import {css} from 'lit' + +export default css` + .star-select-title { + color: var(--dialog-heading); + font-size: var(--auto-28px); + font-weight: bold; + text-align: center; + line-height: var(--auto-36px); + max-height: var(--auto-524px); + } + + main { + --oc-li-padding-inline: var(--auto-60px); + --oc-li-min-height: var(--auto-90px); + padding: 0; + } + + star-radio-group { + width: 100%; + overflow: auto; + max-height: calc(var(--auto-90px) * 4); + } + + star-radio-group[multiple] star-li[checked] { + --li-label: var(--theme-blue); + } + + star-card { + padding: 10px 0; + } +` diff --git a/src/components/dialog/select-dialog.ts b/src/components/dialog/select-dialog.ts new file mode 100644 index 0000000..f24504c --- /dev/null +++ b/src/components/dialog/select-dialog.ts @@ -0,0 +1,291 @@ +import { + customElement, + html, + nothing, + query, + state, + CSSResultArray, + TemplateResult, +} from '@star-web-components/base/star-base-element.js' +import StarBaseDialog from './base-dialog.js' +import selectDialogStyles from './select-dialog.css.js' +import '@star-web-components/button/button.js' +import '@star-web-components/button-group/button-group.js' +import '@star-web-components/radio' +import {StarButton} from '@star-web-components/button' +import {StarLi} from '@star-web-components/li' +import {StarRadioGroup} from '@star-web-components/radio' + +interface SelectDialogOptions { + title?: string + type: string | 'select' + subtitle?: string + image?: string + cancel?: string + confirm?: string + oncancel?: () => void + onconfirm?: (e: Event) => void + onselect?: (e: Event) => void + multiple?: boolean + optionsSlot: StarLi[] | TemplateResult +} + +/** + * 租借子节点 + * + * 将一组元素租给一个目标元素,在该组元素的原位置使用注释占位符进行替代, + * 并返回一个归还节点的方法. + * + * 原理: 剖析单个元素的租借过程 + * 1. 找到元素的父节点(parentElement 或 getRootNode()) + * 2. 在元素的父节点处将元素替换为注释占位符的节点拷贝 + * 3. 在目标位置插入租借来的元素 + * 4. 返回归还租借的子节点的方法 + */ +export const reparentChildren = ( + srcElements: T[], + destination: Element, + { + position, + prepareCallback, + }: { + position: InsertPosition + prepareCallback?: (el: T) => ((el: T) => void) | void + } = { + position: 'beforeend', + } +): (() => T[]) => { + let {length} = srcElements + if (length === 0) return () => srcElements + + let step = 1 + let index = 0 + + if (position === 'afterbegin' || position === 'afterend') { + step = -1 + index = length - 1 + } + + const placeholderItems = new Array(length) + const cleanupCallbacks = new Array<(el: T) => void>(length) + const placeholderTemplate: Comment = document.createComment( + 'placeholder for reparented element' + ) + + do { + const srcElement = srcElements[index] + if (prepareCallback) { + cleanupCallbacks[index] = prepareCallback(srcElement) as (el: T) => void + } + placeholderItems[index] = placeholderTemplate.cloneNode() as Comment + + const parentElement = srcElement.parentElement || srcElement.getRootNode() + if (parentElement && parentElement !== srcElement) { + parentElement.replaceChild(placeholderItems[index], srcElement) + } + destination.insertAdjacentElement(position, srcElement) + index += step + } while (--length > 0) + + return (): T[] => + restoreChildren(placeholderItems, srcElements, cleanupCallbacks) +} + +/** + * 归还子节点 + * + * 是租借子节点的逆过程, 将注释占位符替换成子节点, 再删除注释占位符 + */ +export const restoreChildren = ( + placeholderItems: Comment[], + srcElements: T[], + cleanupCallbacks: ((el: T) => void)[] = [] +): T[] => { + for (let index = 0; index < srcElements.length; ++index) { + const srcElement = srcElements[index] + const placeholderItem = placeholderItems[index] + const parentElement = + placeholderItem.parentElement || placeholderItem.getRootNode() + cleanupCallbacks[index]?.(srcElement) + if (parentElement && parentElement !== placeholderItem) { + parentElement.replaceChild(srcElement, placeholderItem) + } + delete placeholderItems[index] + } + return srcElements +} + +@customElement('star-select-dialog') +export class StarSelectDialog extends StarBaseDialog { + constructor(obj: SelectDialogOptions) { + super() + this.title = obj.title || this.title + this.type = obj.type + this.cancel = obj.cancel || '取消' + this.confirm = obj.confirm || '确定' + this.oncancel = obj.oncancel || (() => {}) + this.onconfirm = obj.onconfirm || (() => {}) + this.onselect = obj.onselect || (() => {}) + this.optionsSlot = obj.optionsSlot + this.multiple = obj.multiple || false + } + + public static override get styles(): CSSResultArray { + return [super.styles, selectDialogStyles] + } + + @state() type!: string + + @state() multiple = false + + @state() title = '提示' + + @query('star-radio-group') radioGroup!: StarRadioGroup + + @query("star-button[variant='primary']") confirmButton!: StarButton + + text!: string + + optionsSlot!: StarLi[] | TemplateResult + + /* 记录初始化的选择项,用于取消时重置 */ + initSelected!: string + + reparentThenRestore!: Function + + @state() optionsTemplateResult: TemplateResult | typeof nothing = nothing + + protected override get headerContent(): TemplateResult { + return html` +
+
${this.title}
+
+ ` + } + + /** + * 针对用户代理的select, 填充基本款: + * + * + * ... + * + * + * 针对自定义的select, 从来源处租借符合radio特征的slot插槽中的选择项: + * + * + * ... + * + */ + protected override get mainContent(): TemplateResult { + return html` + + ${Array.isArray(this.optionsSlot) ? nothing : this.optionsSlot} + + ` + } + + /** + * 针对不同的选择器类型:使用不同的底部按钮组 + * + * + */ + protected override get bottomContent(): TemplateResult { + switch (this.type) { + case 'select': { + if (this.multiple === false) { + return html` + + + + ` + } else { + return html` + + + + + ` + } + } + default: + throw new Error("Unhandled type in SelectDialog' bottom content") + } + } + + /** + * 从 multiple 模式中退出时,重置 StarRadioGroup 中的选择项 + */ + handleCancel(e: Event) { + if (this.multiple === true) { + this.radioGroup.setSelected(this.initSelected) + } + super.handleCancel(e) + this.reparentThenRestore?.() + } + + override handleConfirm(e: Event) { + super.handleConfirm(e) + this.reparentThenRestore?.() + } + + handleSelect(e: Event) { + this.onselect?.(e) + if (this.multiple === false) { + super.handleCancel(e) + this.reparentThenRestore?.() + } else { + if (this.radioGroup.getSelected() === '') { + this.confirmButton.setAttribute('disabled', '') + } else { + this.confirmButton.removeAttribute('disabled') + } + } + } + + protected firstUpdated(_: any) { + super.firstUpdated(_) + if (Array.isArray(this.optionsSlot)) { + this.reparentThenRestore = reparentChildren( + this.optionsSlot, + this.radioGroup + ) + } else { + // 再将 star-radio-group 下的内容解析为数组,重置于 optionsSlot 上 + this.optionsSlot = [ + ...this.querySelectorAll('star-radio-group *'), + ] as StarLi[] + } + + for (const radio of this.optionsSlot) { + if (radio.checked === true) { + this.updateComplete.then(() => { + // 平滑滚动至默认选择项 + radio.scrollIntoView({behavior: 'smooth'}) + // 保存初始化的选项 + this.initSelected = this.radioGroup.getSelected() + }) + return + } + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'star-select-dialog': StarSelectDialog + } +} diff --git a/src/components/dialog/svg/close_lm.svg b/src/components/dialog/svg/close_lm.svg deleted file mode 100644 index 14e2ae4..0000000 --- a/src/components/dialog/svg/close_lm.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/components/fonts/star-icons.ttf b/src/components/fonts/star-icons.ttf index 869a2ac..923d7bb 100644 Binary files a/src/components/fonts/star-icons.ttf and b/src/components/fonts/star-icons.ttf differ diff --git a/src/components/li/README-settings.md b/src/components/li/README-settings.md index 598ae1f..f7b2fff 100644 --- a/src/components/li/README-settings.md +++ b/src/components/li/README-settings.md @@ -76,18 +76,6 @@ ``` -``` -## 电池 --------- ---------------- -| Icon | | 主Label | 分隔线 灰色状态值VALUE --------- ---------------- - -########################################----SLOT ----------------- -| 主Label | Slot-选择器Selector(值选择器, 时间选择器, 日期选择器) ----------------- -``` - ## (inline)内联及模拟 ### type="checkbox" @@ -291,6 +279,55 @@ ``` +### type="selector" 中与 select 标签的交互 + +结构: + +``` +┌───────────────┬──────────────────────────────────────────────────┐ +│ │ ┌─────────┐ ┌───────┬────────┐ ┌───────────────┐ │ +│ │ │ │ │ Label │ Square │ │ │ │ +│ │ │(App)Icon│ ├───────┴────────┤ │ Select │ │ +│type='selector'│ │ │ │ Description | │Single/Multiple│ │ +│ │ └─────────┘ └────────────────┘ └───────────────┘ │ +└───────────────┴──────────────────────────────────────────────────┘ +``` + +基本示例: + +```html + + + + + + + + +``` + +注意: + +1. selected 优先级: star-li > select, select 上的 selected 可被 star-li 覆写 +2. 点击 star-li 的选择器区域时,用户的实际点击事件将到达 select 标签(非 trusted 的 click 事件将无法打开用户代理的 select 窗) + +实际显示的弹窗内的 dom 结构: + +```html + + + + + + + + + + + + +``` + ## 完全插槽态 ### type='base' `star-card` 在 `star-li` 中 diff --git a/src/components/li/li.css.ts b/src/components/li/li.css.ts index d940ac2..95b6b9d 100644 --- a/src/components/li/li.css.ts +++ b/src/components/li/li.css.ts @@ -5,7 +5,7 @@ export default css` display: flex; padding-inline: var(--oc-li-padding-inline, var(--auto-48px)); width: calc(100% - var(--oc-li-padding-inline, var(--auto-48px)) * 2); - min-height: var(--auto-96px); + min-height: var(--oc-li-min-height, var(--auto-96px)); } li { width: 100%; @@ -105,7 +105,7 @@ export default css` span#label { font-weight: bold; color: var(--li-label); - font-weight: 400px; + font-weight: 400; font-size: var(--auto-26px); line-height: var(--auto-34px); } @@ -118,7 +118,7 @@ export default css` margin: auto var(--auto-16px) var(--auto-1px) var(--auto-16px); padding-inline: var(--auto-8px); line-height: var(--auto-23px); - font-weight: 500px; + font-weight: 500; font-size: var(--auto-20px); color: var(--li-square); } @@ -129,9 +129,9 @@ export default css` } span#description { color: var(--li-description); - font-weight: 400px; + font-weight: 400; font-size: var(--auto-20px); - line-height: var(--auto-18px); + line-height: var(--auto-20px); margin-top: var(--auto-12px); } span#value { @@ -148,6 +148,9 @@ export default css` :host([variant='primary']) span#value { color: var(--li-value-primary); } + :host([variant='warn']) span#value { + color: var(--li-value-warn); + } /* 边界折叠 */ span#label, @@ -180,7 +183,7 @@ export default css` box-sizing: border-box; vertical-align: top; text-align: right; - font-weight: 400px; + font-weight: 400; font-size: var(--auto-26px); line-height: var(--auto-26px); color: var(--li-input); @@ -202,6 +205,7 @@ export default css` content: ' '; width: var(--auto-32px); height: var(--auto-32px); + min-width: var(--auto-32px); margin: auto 0px auto auto; box-sizing: border-box; border: var(--auto-3px) solid rgba(38, 38, 38, 0.2); @@ -335,7 +339,7 @@ export default css` --oc-text-padding-inline: 0px; --oc-text-min-width: min-width; margin: auto 0px auto auto; - font-weight: 400px; + font-weight: 400; font-size: var(--auto-26px); line-height: var(--auto-34px); color: var(--font-auxiliary-black); @@ -343,6 +347,12 @@ export default css` ::slotted(star-slider[slot='slider']) { width: 100%; } + ::slotted(star-select[slot='select']) { + margin: auto 0px auto auto; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } /* 条目按压和释放时的变化 */ :host(.starpress:not([type='base'], [type='embed-switch'], [type='embed-checkbox'])) { diff --git a/src/components/li/li.ts b/src/components/li/li.ts index abf477c..eedd11f 100644 --- a/src/components/li/li.ts +++ b/src/components/li/li.ts @@ -26,7 +26,7 @@ export enum LiType { CHECKBOX = 'checkbox', INPUT = 'input', RADIO = 'radio', - /* todo */ SELECTOR = 'selector', + SELECTOR = 'selector', SWITCH = 'switch', /* 嵌入式 */ @@ -313,6 +313,10 @@ export class StarLi extends StarBaseElement { return html` ` + case LiType.SELECTOR: + return html` + + ` case LiType.SWITCH: return html` { + if (child instanceof StarRadio) { + return true + } else if (child.type === 'radio' && child.checked === true) { + /* 非 , 但是具有同样表现的标签扩展 */ + return true + } + return false + }) + } + + public get label(): string { + return [...this.activeRadios].reduce((sum, radio) => { + if (sum === '') return radio.label + else return (sum += ',' + radio.label) + }, '') + } + + public setSelected(str: string): boolean { + try { + const selected = str.split(',') + for (const radio of this.radios) { + if (selected.includes(radio.value)) { + radio.checked = true + } else { + radio.checked = false + } + } + this.emit('change') + return true + } catch (err) { + console.error(err) + return false + } + } + + public getSelected(): string { + return [...this.activeRadios].reduce((sum, radio) => { + if (sum === '') return radio.value + else return (sum += ',' + radio.value) + }, '') + } + render() { return html`
@@ -52,7 +104,7 @@ export class StarRadioGroup extends LitElement { ` } - handleEvent(evt: Event) { + _onSingleRadioClick(evt: Event) { const target = evt.target as StarRadio if (this.radios.indexOf(target) !== -1 && target.checked !== true) { target.checked = true @@ -61,18 +113,26 @@ export class StarRadioGroup extends LitElement { radio.checked = false } } - this.selected = target.value - this.dispatchEvent( - new Event('change', { - bubbles: true, - composed: false, - }) - ) + this.emit('change') } } - protected override firstUpdated(changes: PropertyValues): void { - super.firstUpdated(changes) + _onMultipleRadioClick(evt: Event) { + const target = evt.target as StarRadio + if (this.radios.indexOf(target) !== -1) { + target.checked = !target.checked + this.emit('change') + } + } + + handleEvent(evt: Event) { + ;(this.multiple + ? this._onMultipleRadioClick + : this._onSingleRadioClick + ).call(this, evt) + } + + protected override firstUpdated(): void { for (const radio of this.radios) { if (radio.value === this.selected) { radio.checked = true diff --git a/src/components/select/README.md b/src/components/select/README.md index 1ab43dc..c3190d2 100644 --- a/src/components/select/README.md +++ b/src/components/select/README.md @@ -1,8 +1,155 @@ # 选择 -如: iPhone-通用-键盘-听写语言 +star-select 的作用: 将原生 \ 标签包装为自定义的 UI 设计样式. ## 特点 - 支持多选 - 强制多选类型`的情况下,选择只有一个时,将其置灰 + +## 数据结构 + +### detail.type="SELECT" && detail.choices.multiple: false + +`isFocus``js +{ +bubbles: false +cancelBubble: false +cancelable: false +composed: false +composedTarget: ... +currentTarget: ... +defaultPrevented: false +defaultPreventedByChrome: false +defaultPreventedByContent: false +detail: { +activeEditable: Object { native: XPCWrappedNative_NoHelper } +​​​choices: XPCWrappedNative_NoHelper { +... +multiple: false, +choices: (5) [XPCWrappedNative_NoHelper, XPCWrappedNative_NoHelper, XPCWrappedNative_NoHelper, …], +... +} +imeGroup: null +inputMode: null +inputType: null +isFocus: true +lang: null +lastImeGroup: "" +max: null +maxLength: null +min: null +name: null +selectionEnd: 0 +selectionStart: 0 +type: "SELECT" +value: "" +voiceInputSupported: false +} +eventPhase: 2 +explicitOriginalTarget: ... +isReplyEventFromRemoteContent: false +isSynthesized: false +isTrusted: true +isWaitingReplyFromRemoteContent: false +multipleActionsPrevented: false +originalTarget: ... +returnValue: true +srcElement: ... +target: ... +timeStamp: 80988671.942999 +type: "\_inputmethod-contextchange" +} + +```` + +### detail.type="SELECT" && detail.choices.multiple: true + +```js +​choices: XPCWrappedNative_NoHelper { + ... + multiple: true, + choices: (5) [XPCWrappedNative_NoHelper, XPCWrappedNative_NoHelper, XPCWrappedNative_NoHelper, …], + ... +} +```` + +### detail.type="INPUT" + +#### detail.inputType="time" + +```js +detail: { + activeEditable: {…} + choices: XPCWrappedNative_NoHelper + imeGroup: null + inputMode: null + inputType: "time" + isFocus: true + lang: null + lastImeGroup: "" + max: null + maxLength: null + min: null + name: null + selectionEnd: 0 + selectionStart: 0 + type: "INPUT" + value: "" + voiceInputSupported: false +} +``` + +#### detail.inputType="time" && value="xxxx" + +```js +detail: { + ... + inputType: "time" + ... + type: "INPUT" + ... + value: "10:14" +} +``` + +#### detail.inputType="date" + +```js +detail: { + ... + inputType: "date" + ... + type: "INPUT" +} +``` + +#### detail.inputType="date" && value="xxxx" + +```js +detail { + ... + inputType: "date" + ... + type: "INPUT" + ... + value: "2016-05-20" +} +``` + +#### detail.type="datetime-local" + +``` +detail { + ...​ + inputType: "datetime-local" + ... + type: "INPUT" +} +``` + +系统 UI 框架, 将先跳出选择日期选择窗, 再跳出选择时间窗. + +#### [DEPRECATED]detail.type="datetime" + +`` 已被废弃. diff --git a/src/components/select/index.ts b/src/components/select/index.ts new file mode 100644 index 0000000..a238dc9 --- /dev/null +++ b/src/components/select/index.ts @@ -0,0 +1 @@ +export * from './select.js' diff --git a/src/components/select/package.json b/src/components/select/package.json new file mode 100644 index 0000000..27738a5 --- /dev/null +++ b/src/components/select/package.json @@ -0,0 +1,22 @@ +{ + "name": "@star-web-components/select", + "version": "0.0.1", + "description": "", + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": { + "default": "./index.js" + }, + "./index": { + "default": "./index.js" + }, + "./select.js": { + "default": "./select.js" + }, + "./package.json": "./package.json" + }, + "author": "", + "license": "ISC" +} diff --git a/src/components/select/select.css.ts b/src/components/select/select.css.ts new file mode 100644 index 0000000..04052bc --- /dev/null +++ b/src/components/select/select.css.ts @@ -0,0 +1,51 @@ +import {css} from 'lit' + +export default css` + :host { + display: block; + width: min-content; + } + + a { + position: relative; + display: flex; + align-items: center; + font-weight: 400; + font-size: var(--auto-26px); + line-height: var(--auto-26px); + color: var(--theme-blue); + } + + a::after { + content: 'selector'; + font-family: 'star-icons'; + font-size: var(--auto-32px); + min-width: var(--auto-32px); /* 应对可能的被挤压 */ + margin-left: var(--auto-14px); + color: var(--selector-icon-color); + } + + :host([variant='rightarrow']) a::after { + content: 'right-light'; + } + + span { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + margin-left: auto; + } + + select { + opacity: 0; + position: absolute; + left: 0; + width: 100%; + height: 100%; + } + + ::slotted(*) { + display: none; + } +` diff --git a/src/components/select/select.ts b/src/components/select/select.ts new file mode 100644 index 0000000..10bbc25 --- /dev/null +++ b/src/components/select/select.ts @@ -0,0 +1,181 @@ +import { + customElement, + html, + property, + queryAssignedElements, + state, + CSSResult, + StarBaseElement, +} from '@star-web-components/base' +import selectStyles from './select.css.js' +import {StarSelectDialog} from '@star-web-components/dialog' +import {StarLi} from '@star-web-components/li' +import {StarRadio, StarRadioGroup} from '@star-web-components/radio' + +export type RadioVariant = 'circle' | 'checkmark' + +@customElement('star-select') +export class StarSelect extends StarBaseElement { + public static override get styles(): CSSResult { + return selectStyles + } + + @property({type: String, reflect: true}) selected!: string + + /* 提供给选择器窗的标题 */ + @property({type: String}) title!: string + + @property({type: Boolean, reflect: true}) multiple = false + + @property({type: Object}) data!: [ + [ + { + label?: string + value: string | number + } + ] + ] + + /* 用于指示自身风格,包括: selector(默认), rightarrow, */ + @property({type: String, reflect: true}) variant!: string + + @state() label!: string + + @queryAssignedElements() optionsSlot!: StarLi[] + + /** + * 兼容用户代理支持的分类选择器(带标签) + * + * 但预留分组. + */ + // a!: { + // size?: number, + // multiple?: string, + // options: [ + // { + // label: string + // value: string | number + // }, + // ] | [ + // { + // optgroup: + // } + // ] + + // } + + protected firstUpdated(): void { + // 1. 为 radio 标签,或是有实现类 radio 标签行为的标签 + // 如 + // 2. 不满足1的标签将被删除 + // 3. 如果option未指定label,将使用value值作为label值 + // 4. 将所有 option 的 checked 置为 false + // 5. 如果是多值选择,设置 star-li 的 variant 属性为 "checkmark" + if (this.optionsSlot.length === 0) throw new Error('No legal option') + + for (const option of this.optionsSlot) { + if ( + !( + option instanceof StarRadio || + (option.type === 'radio' && option.value !== undefined) + ) + ) { + option.parentElement?.removeChild(option) + continue + } + + if (option.label === undefined) { + option.label = option.value + } + if (this.multiple === true) { + option.variant = 'checkmark' + } + option.checked = false + } + + // 当使用端未指定 selected 时,将进行如下行为: + // 1. 使用 optionsSlot 中第一项的 value 和 label 作为默认值, + // 当使用端指定了 selected 时,将进行如下行为: + // 1. 遍历 optionsSlot 找到对等项 + // 当经历上述过程后,this.label 还为空,说明 this.selected 为错值 + // 那将继续使用 optionsSlot 中第一项的 value 和 label 作为默认值 + for (const [index, option] of this.optionsSlot.entries()) { + if (this.selected !== undefined) { + if (this.multiple === false) { + if (this.selected === option.value) { + this.label = option.label + option.checked = true + break + } + } else { + if (this.selected.split(',').includes(option.value)) { + if (this.label === undefined) this.label = option.label + else this.label += ',' + option.label + option.checked = true + continue + } + } + } + + if (index === this.optionsSlot.length - 1) { + if (this.selected === undefined || this.label === undefined) { + console.warn('Not find a legal option, we will use the first option') + + this.selected = this.optionsSlot[0].value + this.label = this.optionsSlot[0].label + this.optionsSlot[0].checked = true + } + } + } + } + + /** + * 事件来源:star-radio-group 上的change自定义事件 + */ + _onselect(e: Event) { + const target = e.target as StarRadioGroup + if (this.selected !== target.selected) { + this.selected = target.getSelected() + this.label = target.label + this.emit('change') + } + } + + /** + * 当自身被点击时,打开 ,将 options 租借给它。 + * 完成调用后, 再将 options 归还。 + */ + _onclick(_: Event) { + const starAlertDialog = new StarSelectDialog({ + title: this.title || '测试', + type: 'select', + multiple: this.multiple, + cancel: '取消', + confirm: '确定', + onselect: this._onselect.bind(this), + optionsSlot: this.optionsSlot, + }) + // TODO: 移到overlay层 + document.body?.appendChild(starAlertDialog) + } + + render() { + return html` + + ${this.label} + + + ` + } + + constructor() { + super() + this.addEventListener('click', this._onclick) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'star-select': StarSelect + } +} diff --git a/src/test/panels/fonts/star-icons.ts b/src/test/panels/fonts/star-icons.ts index 311d5b5..ff99bf8 100644 --- a/src/test/panels/fonts/star-icons.ts +++ b/src/test/panels/fonts/star-icons.ts @@ -8,7 +8,6 @@ export class PanelFontsStarIcons extends LitElement { '2g', '3g', '4g', - 'ip', 'accessibility', 'achievement', 'add-contact', @@ -18,8 +17,6 @@ export class PanelFontsStarIcons extends LitElement { 'airplane', 'alarm-clock-stop', 'alarm-clock', - 'alarm-song-shock', - 'alarm-stop-shock', 'alarm-stop', 'alarm', 'album', @@ -48,6 +45,7 @@ export class PanelFontsStarIcons extends LitElement { 'battery-7', 'battery-8', 'battery-9', + 'battery-capacity', 'battery-charging', 'battery-gray-1', 'battery-gray', @@ -83,6 +81,7 @@ export class PanelFontsStarIcons extends LitElement { 'call', 'callback-emergency', 'camera-db', + 'camera-web', 'camera', 'change-wallpaper', 'clear-input-left', @@ -154,6 +153,7 @@ export class PanelFontsStarIcons extends LitElement { 'incoming-sms', 'info', 'invisible', + 'ip', 'keyboard-circle', 'keyboard', 'languages', @@ -170,6 +170,7 @@ export class PanelFontsStarIcons extends LitElement { 'media-mute', 'media-sound', 'media-storage', + 'memory', 'menu', 'message-voice', 'message', @@ -207,6 +208,7 @@ export class PanelFontsStarIcons extends LitElement { 'power', 'privacy', 'qq', + 'ratio', 'reboot', 'recent-calls', 'reduce-db', @@ -217,12 +219,16 @@ export class PanelFontsStarIcons extends LitElement { 'reply-all', 'right-light', 'right', + 'ring-mute-2', + 'ring-mute', + 'ring', 'rocket', 'rotate', 'safe', 'scene', 'screen-projection', 'screen-recording', + 'screen-size', 'screen', 'sd-card', 'search', @@ -230,6 +236,7 @@ export class PanelFontsStarIcons extends LitElement { 'seek-back', 'seek-forward', 'select', + 'selector', 'self-timer', 'send-left', 'send-right', @@ -411,6 +418,8 @@ export class PanelFontsStarIcons extends LitElement { 'update-balance', 'usb', 'user', + 'vibrate-and-ring-mute', + 'vibrate-and-ring', 'vibrate', 'video-mic', 'video-size', diff --git a/src/test/panels/li/li.ts b/src/test/panels/li/li.ts index bc5579f..7c70766 100644 --- a/src/test/panels/li/li.ts +++ b/src/test/panels/li/li.ts @@ -322,13 +322,7 @@ export class PanelLi extends LitElement { variant="circle" vertical > - + - +
+ diff --git a/src/test/panels/select/select.ts b/src/test/panels/select/select.ts new file mode 100644 index 0000000..99656c5 --- /dev/null +++ b/src/test/panels/select/select.ts @@ -0,0 +1,219 @@ +import { + css, + customElement, + html, + LitElement, + CSSResult, +} from '@star-web-components/base' +import '@star-web-components/select' +import {lockscreen, lightmode, darkmode, weibo} from '../li/static/icons' + +@customElement('panel-select') +export class PanelSelect extends LitElement { + render() { + return html` +

star-select

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + alert('selected is:' + e.target.selected)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${lockscreen} + + + ${lockscreen} + + + ${lockscreen} + + + + + + + + + ${lightmode} + + + + + ${darkmode} + + + + + + + + + ${weibo} + + + ${weibo} + + + + ` + } + public static override get styles(): CSSResult { + return css`` + } +} +declare global { + interface HTMLElementTagNameMap { + 'panel-select': PanelSelect + } +}