Skip to content

Commit

Permalink
[duoyun-dui] Add <dy-pat-table>
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Jan 17, 2024
1 parent 6006d66 commit d1e651b
Show file tree
Hide file tree
Showing 19 changed files with 1,109 additions and 95 deletions.
25 changes: 0 additions & 25 deletions packages/duoyun-ui/docs/zh/01-guide/35-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

DuoyunUI 提供一些比较复杂的元素,通常由多个 DuoyunUI 元素组成,使用它们可以快速完成一些通用需求,比如创建一个落地页、控制后台,它们有一些独特的特点:

- 内容渲染成 Light DOM
- 元素名称为 `<dy-pat-*>`
- 元素类名为 `DyPat*Element`

Expand All @@ -29,8 +28,6 @@ render(

> [!NOTE]
>
> - 不要尝试在模式元素中添加子元素
> - 避免使用通用 CSS 选择器以防止意外的样式化模式元素内容
> - [使用 React 组件](./60-integrate.md)时无需指定 `pattern` 路径,模式元素和其他 DuoyunUI 输出在同一个目录中:
> ```js
> import 'duoyun-ui/react/DyPatConsole';
Expand All @@ -40,28 +37,6 @@ render(
> import 'duoyun-ui/svelte/pat-console';
> ```
## 自定义模式元素
要对模式元素的内容进行一些样式定制,可以通过外部 `<style>` 来进行,这得益于模式元素使用 Light DOM,例如:
```js
import 'duoyun-ui/patterns/console';
render(
html`
<style>
dy-pat-console .sidebar {
width: 21em;
}
</style>
<dy-pat-console></dy-pat-console>
`,
document.body,
);
```
当然,更强大的方式是复制粘贴。

## 例子
<iframe src="https://examples.gemjs.org/console" loading="lazy"></iframe>
9 changes: 5 additions & 4 deletions packages/duoyun-ui/src/elements/badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const style = createCSSSheet(css`
justify-content: center;
font-size: 0.75em;
white-space: nowrap;
border: 2px solid currentColor;
box-shadow: 0px 0px 0px 2px;
border-radius: 100em;
color: ${theme.backgroundColor};
height: 1.5em;
Expand All @@ -48,7 +48,7 @@ const style = createCSSSheet(css`
width: 1em;
}
@supports (anchor-name: --foo) {
.badge {
:host(:not(:where([data-inline], :state(inline)))) .badge {
position: absolute;
anchor-default: --anchor;
top: anchor(top);
Expand Down Expand Up @@ -89,7 +89,7 @@ export class DuoyunBadgeElement extends GemElement {
@part static badge: string;

@attribute color: StringList<'positive' | 'informative' | 'notice'>;
@numattribute count: number;
@attribute count: number | string;
@boolattribute dot: boolean;
@boolattribute small: boolean;
@numattribute max: number;
Expand All @@ -109,13 +109,14 @@ export class DuoyunBadgeElement extends GemElement {
}

mounted = () => {
this.inline = !this.childNodes.length;
this.slotRef.element?.addEventListener('slotchange', () => {
this.inline = !getAssignedElements(this.slotRef.element!).length;
});
};

render = () => {
const value = this.count > this.#max ? `${this.#max}+` : `${this.count}`;
const value = Number(this.count) > this.#max ? `${this.#max}+` : `${this.count}`;
return html`
<slot ref=${this.slotRef.ref}></slot>
${this.count || this.icon || this.dot
Expand Down
20 changes: 20 additions & 0 deletions packages/duoyun-ui/src/elements/base/resettable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cleanObject } from '@mantou/gem/lib/utils';
import { GemElement } from '@mantou/gem/lib/element';

export class DuoyunResettableBaseElement<T = Record<string, unknown>> extends GemElement<T> {
constructor() {
super();
this.effect(
() => (this.#initState = { ...this.state }),
() => [],
);
}

#initState: T;
state: T;

reset = () => {
cleanObject(this.state!);
this.setState({ ...this.#initState });
};
}
3 changes: 2 additions & 1 deletion packages/duoyun-ui/src/elements/base/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export function resizeObserver(ele: DuoyunResizeBaseElement, options: { throttle
return () => ro.disconnect();
}

export type DuoyunResizeBaseElementOptions = GemElementOptions & { throttle?: boolean };
export class DuoyunResizeBaseElement<_T = Record<string, unknown>> extends GemElement {
@emitter resize: Emitter<ResizeDetail>;

constructor(options: GemElementOptions & { throttle?: boolean } = {}) {
constructor(options: DuoyunResizeBaseElementOptions = {}) {
super(options);
this.effect(
() => resizeObserver(this, options),
Expand Down
5 changes: 2 additions & 3 deletions packages/duoyun-ui/src/elements/base/scroll.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { adoptedStyle, state } from '@mantou/gem/lib/decorators';
import { GemElementOptions } from '@mantou/gem/lib/element';
import { createCSSSheet, css } from '@mantou/gem/lib/utils';

import { DuoyunResizeBaseElement } from './resize';
import { DuoyunResizeBaseElement, DuoyunResizeBaseElementOptions } from './resize';

const PIXEL_DEVIATION = 0.1;

Expand Down Expand Up @@ -63,7 +62,7 @@ export class DuoyunScrollBaseElement<_T = Record<string, unknown>> extends Duoyu
@state bottomOverflow: boolean;
@state leftOverflow: boolean;

constructor(options?: GemElementOptions) {
constructor(options?: DuoyunResizeBaseElementOptions) {
super(options);
this.addEventListener('scroll', this.#check);
this.addEventListener('scrollend', this.#check);
Expand Down
5 changes: 3 additions & 2 deletions packages/duoyun-ui/src/elements/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const style = createCSSSheet(css`
* @attr name
* @attr value
* @attr type
* @attr placeholder
* @attr disabled
* @attr autofocus
* @attr clearable
Expand All @@ -160,7 +161,7 @@ export class DuoyunInputElement extends GemElement {

@refobject inputRef: RefObject<HTMLInputElement>;
@globalemitter change: Emitter<string>;
@emitter clear: Emitter;
@emitter clear: Emitter<string>;

@attribute name: string;
@attribute value: string;
Expand Down Expand Up @@ -283,7 +284,7 @@ export class DuoyunInputElement extends GemElement {

#onClear = (evt: Event) => {
evt.stopPropagation();
this.clear(null);
this.clear('');
this.focus();
this.#history.save();
};
Expand Down
66 changes: 62 additions & 4 deletions packages/duoyun-ui/src/elements/side-navigation.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import { connectStore, adoptedStyle, customElement, property, part } from '@mantou/gem/lib/decorators';
import { connectStore, adoptedStyle, customElement, property, part, state } from '@mantou/gem/lib/decorators';
import { html, TemplateResult } from '@mantou/gem/lib/element';
import { history } from '@mantou/gem/lib/history';
import { createCSSSheet, css, QueryString } from '@mantou/gem/lib/utils';

import { theme } from '../lib/theme';
import { commonHandle } from '../lib/hotkeys';
import { focusStyle } from '../lib/styles';
import { createSVGFromText } from '../lib/image';
import { commonColors } from '../lib/color';

import { createPath, matchPath } from './route';
import { DuoyunScrollBaseElement } from './base/scroll';

import './link';
import './use';

const cache = new Map<string, string>();
function getIcon(title: string) {
const icon =
cache.get(title) ||
createSVGFromText(title.replaceAll(/[^\w]/g, ''), {
translate: [0, 0],
rotate: 0,
colors: commonColors,
});
cache.set(title, icon);
return icon;
}

interface Item {
icon?: string | Element | DocumentFragment;
title?: string;
slot?: TemplateResult;
pattern?: string;
Expand Down Expand Up @@ -47,7 +64,9 @@ const style = createCSSSheet(css`
}
.item {
display: flex;
gap: 1em;
justify-content: space-between;
align-items: center;
position: relative;
padding: 0.5em 0.75em;
font-size: 0.875em;
Expand All @@ -56,7 +75,23 @@ const style = createCSSSheet(css`
text-decoration: none;
line-height: 1.2;
}
.children .item {
.title-wrap {
display: flex;
align-items: center;
gap: 1em;
width: 0;
flex-grow: 1;
}
.icon {
flex-shrink: 0;
width: 1.2em;
}
.title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
:host(:not(:where([data-compact], :state(:compact)))) .children .item {
padding-inline-start: calc(0.75em + 1em);
}
.item:where(:hover, [data-active], :state(active)) {
Expand All @@ -78,6 +113,12 @@ const style = createCSSSheet(css`
font-size: 0.7em;
color: ${theme.describeColor};
}
:host(:where([data-compact], :state(:compact))) :where(.group-title, .title, .title-wrap + *) {
display: none;
}
:host(:where([data-compact], :state(:compact))) :where(.item, .title-wrap) {
justify-content: center;
}
`);

type State = Record<string, boolean>;
Expand All @@ -97,6 +138,8 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement<State>

@property items?: NavItems = [];

@state compact: boolean;

// children open state
state: State = {};

Expand Down Expand Up @@ -128,7 +171,17 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement<State>
this.items?.forEach(matchChildren);
};

#renderItem = ({ pattern, title = '<No Title>', slot, params, query, hash, children }: Item): TemplateResult => {
#renderItem = ({
pattern,
title = '<No Title>',
slot,
params,
query,
hash,
children,
icon,
}: Item): TemplateResult => {
const ico = icon || (this.compact && getIcon(title));
return html`
<dy-active-link
class="item"
Expand All @@ -142,7 +195,11 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement<State>
hash=${hash || ''}
pattern=${!pattern ? '' : pattern.endsWith('*') ? pattern : `${pattern}/*`}
>
<span>${title}</span>${slot}
<div class="title-wrap">
${ico ? html`<dy-use class="icon" .element=${ico}></dy-use>` : ''}
<span class="title">${title}</span>
</div>
${slot}
</dy-active-link>
${children && this.state[title] ? html`<div class="children">${children.map(this.#renderItem)}</div>` : ''}
`;
Expand All @@ -157,6 +214,7 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement<State>

render = () => {
if (!this.items?.length) return html``;
this.compact = this.contentRect.width < 100;
return html`${this.items.map((item) =>
'group' in item
? html`
Expand Down
1 change: 1 addition & 0 deletions packages/duoyun-ui/src/elements/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type PresetColor = 'positive' | 'informative' | 'negative' | 'notice' | '
/**
* @customElement dy-tag
* @attr small
* @attr closable
* @fires close
*/
@customElement('dy-tag')
Expand Down
18 changes: 18 additions & 0 deletions packages/duoyun-ui/src/lib/encode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { pseudoRandom } from './number';

function safeUrlToBase64Str(str: string) {
return str.replaceAll('-', '+').replaceAll('_', '/');
}
Expand Down Expand Up @@ -44,3 +46,19 @@ export async function hash(strOrAb: string | ArrayBuffer, output: 'string' | 'ar
if (output === 'arrayBuffer') return buffer;
return [...new Uint8Array(buffer)].map((e) => e.toString(16).padStart(2, '0')).join('');
}

// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/**output int */
export function fnv1a(str: string) {
const FNV_OFFSET_BASIS = 2166136261;
const FNV_PRIME = 16777619;

let hash = FNV_OFFSET_BASIS;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash *= FNV_PRIME;
}

// 减少连续性
return pseudoRandom(Math.abs(hash))();
}
Loading

0 comments on commit d1e651b

Please sign in to comment.