(feature)添加star-select及star-select-dialog,添加star-li type='selector',更新star-icons.ttf

This commit is contained in:
wangchangqi 2022-11-28 15:26:56 +08:00
parent a626003480
commit 192ea982ee
23 changed files with 1270 additions and 67 deletions

View File

@ -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);

View File

@ -10,6 +10,7 @@
| -------------- | ------- | -------- | -------------------------- |
| vertical | Boolean | false | 用于指示是否显示垂直按钮组 |
| split | Boolean | false | 用于指示是否显示分隔符 |
| inheritRadius | Boolean | false | 用于指示是否继承倒角 |
<center><b>star-button-group组件支持的插槽</b></center>
@ -38,6 +39,11 @@ button-group 将 100% 填充父容器所给的空间,再均匀平铺\<slot\>
<star-button type="text" variant="default" label="取消"></star-button>
<star-button type="text" variant="primary" label="确定"></star-button>
</star-button-group>
<star-button-group vertical split inheritRadius>
<star-button type="text" variant="default" label="取消"></star-button>
<star-button type="text" variant="primary" label="确定"></star-button>
</star-button-group>
```
## 注意

View File

@ -4,6 +4,7 @@ export default css`
:host {
--background-image-url: '';
flex: 1;
display: block;
}
div {

View File

@ -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 = ''

View File

@ -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
<!-- 基础选择器 -->
<select id="singleSel">
<option value="Mozilla">Mozilla</option>
<option value="B2G">B2G</option>
<option value="Gaia">Gaia</option>
<option value="Gecko">Gecko</option>
<option value="Basecamp">Basecamp</option>
</select>
<!-- 带size属性 -->
<select size="3" id="sizeSel">
<option value="Mozilla">Mozilla</option>
<option value="B2G">B2G</option>
<option value="Gaia">Gaia</option>
<option value="Gecko">Gecko</option>
<option value="Basecamp">Basecamp</option>
</select>
<!-- 多选选择器, 待mulitiple属性 -->
<select multiple="multiple" id="multiSel">
<option value="Mozilla">Mozilla</option>
<option value="B2G">B2G</option>
<option value="Gaia">Gaia</option>
<option value="Gecko">Gecko</option>
<option value="Basecamp">Basecamp</option>
</select>
<!-- 分类选择器, 子标签是<optgroup> -->
<select name="groups" size="6">
<optgroup label="B2G">
<option value="Gonk">Gonk</option>
<option value="Gecko">Gecko</option>
<option value="Gaia">Gaia</option>
</optgroup>
<optgroup label="Devices">
<option value="otoro">Otoro</option>
<option value="hamachi">Hamachi</option>
<option value="flame">Flame</option>
</optgroup>
</select>
```
### 时间日期选择器
```html
<!-- 单时间 -->
<input type="time" placeholder="Time" />
<!-- 单日期 -->
<input type="date" placeholder="Date" />
<!-- 预设值 -->
<input type="date" placeholder="Time" value="2016-05-20" />
<input type="time" placeholder="Time" value="10:14" />
```
### UI 设计内容
触发形态:
```
```
```
┌───────────┐
| Header |
├───────────|
| ┌───────┐ |
| │ Title │ |
| └───────┘ |
└───────────┘
┌─────────────────────────────────────────────────┐
| Content |
├─────────────────────────────────────────────────|
│ ┌─single select──────────────────┐ │
│ │ │ │
│ │ ┌────────────┐ │ │
│ │ │ RadioGroup │ │ │
│ │ └────────────┘ │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
┌───────────────────────────────────┐
| Footer |
├───────────────────────────────────|
│ ┌───────────────────────────────┐ │
│ │ StarButtonGroup │ │
│ ├─────────single select─────────| │
│ │ ┌────────────┐ │ │
│ │ │ StarButton │ │ │
│ │ └────────────┘ │ │
│ ├───────multiple select─────────| │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ StarButton │ │ StarButton │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────┘
```
### 转换
## TBD: 使用用户代理支持的<dialog>
使用浏览器用户代理支持的<dialog>时,可以省去自行设定模态的步骤,并且其自身已经拥有了默认排版,省去设置样式的行为。

View File

@ -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'

View File

@ -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;
}
`

View File

@ -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 = <T extends Element>(
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<Comment>(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<T>(placeholderItems, srcElements, cleanupCallbacks)
}
/**
*
*
* , ,
*/
export const restoreChildren = <T extends Element>(
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`
<div class="star-base-main">
<div class="star-select-title">${this.title}</div>
</div>
`
}
/**
* select,
* <star-raido-gorup>
* <star-radio>
* ...
* </star-radio-group>
*
* select, radio特征的slot插槽中的选择项
* <star-raido-gorup>
* <mock-radio...>
* ...
* </star-radio-group>
*/
protected override get mainContent(): TemplateResult {
return html`
<star-radio-group @change=${this.handleSelect} ?multiple=${this.multiple}>
${Array.isArray(this.optionsSlot) ? nothing : this.optionsSlot}
</star-radio-group>
`
}
/**
* 使
*
*
*/
protected override get bottomContent(): TemplateResult {
switch (this.type) {
case 'select': {
if (this.multiple === false) {
return html`
<star-button-group inheritRadius>
<star-button
type="text"
label=${this.cancel}
variant="primary"
@click=${this.handleCancel}
></star-button>
</star-button-group>
`
} else {
return html`
<star-button-group inheritRadius split>
<star-button
type="text"
label=${this.cancel}
@click=${this.handleCancel}
></star-button>
<star-button
type="text"
label=${this.confirm}
variant="primary"
@click=${this.handleConfirm}
></star-button>
</star-button-group>
`
}
}
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
}
}

View File

@ -1,10 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_152_6195)">
<path d="M7 25L25 7M7 7L25 25" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_152_6195">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

View File

@ -76,18 +76,6 @@
</star-li>
```
```
## 电池
-------- ----------------
| Icon | | 主Label | 分隔线 灰色状态值VALUE
-------- ----------------
########################################----SLOT
----------------
| 主Label | Slot-选择器Selector(值选择器, 时间选择器, 日期选择器)
----------------
```
## (inline)内联及模拟
### type="checkbox"
@ -291,6 +279,55 @@
</star-radio-group>
```
### type="selector" 中与 select 标签的交互
结构:
```
┌───────────────┬──────────────────────────────────────────────────┐
│ │ ┌─────────┐ ┌───────┬────────┐ ┌───────────────┐ │
<star-li> │ │ │ │ Label │ Square │ │ │ │
│ │ │(App)Icon│ ├───────┴────────┤ │ Select │ │
│type='selector'│ │ │ │ Description | │Single/Multiple│ │
│ │ └─────────┘ └────────────────┘ └───────────────┘ │
└───────────────┴──────────────────────────────────────────────────┘
```
基本示例:
```html
<star-li type="selector" label="自动熄屏" selected="30">
<star-select slot="select" selected="15">
<option label="15秒" value="15"></option>
<option label="30秒" value="30"></option>
<option label="60秒" value="60"></option>
<option label="永不" value="never"></option>
</star-select>
</star-li>
```
注意:
1. selected 优先级: star-li > select, select 上的 selected 可被 star-li 覆写
2. 点击 star-li 的选择器区域时,用户的实际点击事件将到达 select 标签(非 trusted 的 click 事件将无法打开用户代理的 select 窗)
实际显示的弹窗内的 dom 结构:
```html
<star-select-dialog label="自动熄屏">
<header...>
<content>
<star-radio-group select="inherit select">
<option label="15秒" value="15"></option>
<option label="30秒" value="30"></option>
<option label="60秒" value="60"></option>
<option label="永不" value="never"></option>
</star-radio-group>
</content>
<footer...>
</star-select-dialog>
```
## 完全插槽态
### type='base' `star-card``star-li`

View File

@ -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'])) {

View File

@ -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`
<slot name="slider-button"></slot>
`
case LiType.SELECTOR:
return html`
<slot name="select"></slot>
`
case LiType.SWITCH:
return html`
<star-switch
@ -404,9 +408,6 @@ export class StarLi extends StarBaseElement {
/* deprecated */ case LiType.ONLY_READ:
return this.getonlyread()
case LiType.SELECTOR:
// TBD
case LiType.EMBED_CHECKBOX:
case LiType.EMBED_INFO:
case LiType.EMBED_SLIDER:

View File

@ -1,10 +1,16 @@
import {css, html, CSSResultArray, LitElement, PropertyValues} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import {
css,
customElement,
html,
property,
CSSResultArray,
StarBaseElement,
} from '@star-web-components/base/star-base-element.js'
import {StarRadio, RadioVariant} from './radio.js'
import {sharedStyles} from './radio-style.js'
@customElement('star-radio-group')
export class StarRadioGroup extends LitElement {
export class StarRadioGroup extends StarBaseElement {
public static override get styles(): CSSResultArray {
return [
sharedStyles,
@ -26,6 +32,8 @@ export class StarRadioGroup extends LitElement {
// 默认为水平
@property({type: Boolean, reflect: true}) vertical = false
@property({type: Boolean, reflect: true}) multiple = false
/**
* radio , 支持: circle(), checkmark,
* child radio样式.
@ -44,6 +52,50 @@ export class StarRadioGroup extends LitElement {
})
}
public get activeRadios(): StarRadio[] {
return Array.prototype.filter.call(this.children, (child) => {
if (child instanceof StarRadio) {
return true
} else if (child.type === 'radio' && child.checked === true) {
/* 非 <star-radio>, 但是具有同样表现的标签扩展 */
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`
<div id="star-radio-group" @click=${this}>
@ -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

View File

@ -1,8 +1,155 @@
# 选择
如: iPhone-通用-键盘-听写语言
star-select 的作用: 将原生 \<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"
`<input type="datetime">` 已被废弃.

View File

@ -0,0 +1 @@
export * from './select.js'

View File

@ -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"
}

View File

@ -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;
}
`

View File

@ -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[]
/**
* (<optgroup>)
*
* .
*/
// a!: {
// size?: number,
// multiple?: string,
// options: [
// {
// label: string
// value: string | number
// },
// ] | [
// {
// optgroup:
// }
// ]
// }
protected firstUpdated(): void {
// 1. 为 radio 标签,或是有实现类 radio 标签行为的标签
// 如 <star-li type="radio"><star-card type="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')
}
}
/**
* <select-dialog> options
* <select-dialog> 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`
<a>
<span>${this.label}</span>
</a>
<slot><!-- option --></slot>
`
}
constructor() {
super()
this.addEventListener('click', this._onclick)
}
}
declare global {
interface HTMLElementTagNameMap {
'star-select': StarSelect
}
}

View File

@ -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',

View File

@ -322,13 +322,7 @@ export class PanelLi extends LitElement {
variant="circle"
vertical
>
<star-card
type="radio"
label="浅色模式"
variant="radio"
value="light"
checked
>
<star-card type="radio" label="浅色模式" value="light" checked>
<star-svg-icon
slot="image"
style="width:var(--auto-208px);height:var(--auto-202px)"
@ -336,7 +330,7 @@ export class PanelLi extends LitElement {
${lightmode}
</star-svg-icon>
</star-card>
<star-card type="radio" label="深色模式" variant="radio" value="dark">
<star-card type="radio" label="深色模式" value="dark">
<star-svg-icon
slot="image"
style="width:var(--auto-208px);height:var(--auto-202px)"

View File

@ -39,6 +39,7 @@ import './pattern-view/pattern-view'
import './picker/picker'
import './prompt/prompt'
import './radio/radio'
import './select/select'
import './slider/slider'
import './switch/switch'
import './toast/toast'
@ -100,6 +101,11 @@ export class PanelRoot extends LitElement {
<star-ul type=${UlType.ONLY_HEADER} title="复合组件">
<star-li label="StarLi-条目" icon="play-circle" href="#li"></star-li>
<hr />
<star-li
label="StarSelect-选择器"
icon="play-circle"
href="#select"
></star-li>
</star-ul>
<star-ul type=${UlType.ONLY_HEADER} title="功能组件">

View File

@ -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`
<h1>star-select</h1>
<div id="only-select">
<star-select slot="select" selected="30" title="单值选择器">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
<star-select
slot="select"
selected="30"
variant="rightarrow"
title="单值选择器"
>
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
<star-select slot="select" selected="1800" title="时间选项">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="45秒" value="45"></star-li>
<star-li type="radio" label="1分钟" value="60"></star-li>
<star-li type="radio" label="1分半钟" value="90"></star-li>
<star-li type="radio" label="2分钟" value="120"></star-li>
<star-li type="radio" label="3分钟" value="180"></star-li>
<star-li type="radio" label="4分钟" value="240"></star-li>
<star-li type="radio" label="5分钟" value="300"></star-li>
<star-li type="radio" label="10分钟" value="600"></star-li>
<star-li type="radio" label="15分钟" value="900"></star-li>
<star-li type="radio" label="30分钟" value="1800"></star-li>
<star-li type="radio" label="1小时" value="3600"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</div>
<star-li type="selector" label="单值选择器(star-li)">
<star-select
slot="select"
selected="30"
title="时间选项"
@change=${(e: any) => alert('selected is:' + e.target.selected)}
>
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li
type="selector"
label="单值选择器(star-li[variant='rightarrow'])"
>
<star-select
slot="select"
selected="30"
variant="rightarrow"
title="时间选项"
>
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="单值选择器(不指定selected)">
<star-select slot="select" title="时间选项">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="单值选择器(指定错误的selected)">
<star-select slot="select" selected="0" title="时间选项">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="单值选择器(可滚动列表)">
<star-select slot="select" selected="1800" title="时间选项">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="45秒" value="45"></star-li>
<star-li type="radio" label="1分钟" value="60"></star-li>
<star-li type="radio" label="1分半钟" value="90"></star-li>
<star-li type="radio" label="2分钟" value="120"></star-li>
<star-li type="radio" label="3分钟" value="180"></star-li>
<star-li type="radio" label="4分钟" value="240"></star-li>
<star-li type="radio" label="5分钟" value="300"></star-li>
<star-li type="radio" label="10分钟" value="600"></star-li>
<star-li type="radio" label="15分钟" value="900"></star-li>
<star-li type="radio" label="30分钟" value="1800"></star-li>
<star-li type="radio" label="1小时" value="3600"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="多值选择项">
<star-select slot="select" selected="30" multiple title="时间选项">
<star-li type="radio" label="15秒" value="15"></star-li>
<star-li type="radio" label="30秒" value="30"></star-li>
<star-li type="radio" label="60秒" value="60"></star-li>
<star-li type="radio" label="永不" value="never"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="多值选择项">
<star-select
slot="select"
selected="two,three"
multiple
title="操作选项"
>
<star-li type="radio" label="操作一" value="one"></star-li>
<star-li type="radio" label="操作二" value="two"></star-li>
<star-li type="radio" label="操作三" value="three"></star-li>
</star-select>
</star-li>
<star-li type="selector" label="复杂选择项(star-li)">
<star-select slot="select" selected="transfer-photo" title="传输选项">
<star-li type="radio" label="传输文件" value="transfer-file">
<star-svg-icon slot="icon">${lockscreen}</star-svg-icon>
</star-li>
<star-li type="radio" label="传输照片" value="transfer-photo">
<star-svg-icon slot="icon">${lockscreen}</star-svg-icon>
</star-li>
<star-li type="radio" label="仅充电" value="only-charge">
<star-svg-icon slot="icon">${lockscreen}</star-svg-icon>
</star-li>
</star-select>
</star-li>
<star-li type="selector" label="复杂选择项(star-card)">
<star-select
slot="select"
selected="transfer-photo"
title="浅色和深色模式切换"
>
<star-card type="radio" label="浅色模式" value="light" checked>
<star-svg-icon
slot="image"
style="width:var(--auto-208px);height:var(--auto-202px)"
>
${lightmode}
</star-svg-icon>
</star-card>
<star-card type="radio" label="深色模式" value="dark">
<star-svg-icon
slot="image"
style="width:var(--auto-208px);height:var(--auto-202px)"
>
${darkmode}
</star-svg-icon>
</star-card>
</star-select>
</star-li>
<star-li type="selector" label="复杂单值选择器(star-li)">
<star-select
slot="select"
selected="transfer-photo"
title="浅色和深色模式切换"
>
<star-li
type="radio"
label="日出到日落"
value="natural"
square="2.4G/5G"
description="根据所在地的日出和日落时间,自动切换浅色和深色模式"
>
<star-svg-icon slot="icon">${weibo}</star-svg-icon>
</star-li>
<star-li
type="radio"
label="自定义时间"
value="customize"
square="2.4G/5G"
description="根据设置的时间段,自动切换浅色和深色模式"
>
<star-svg-icon slot="icon">${weibo}</star-svg-icon>
</star-li>
</star-select>
</star-li>
`
}
public static override get styles(): CSSResult {
return css``
}
}
declare global {
interface HTMLElementTagNameMap {
'panel-select': PanelSelect
}
}