From af4cc8eec0184991a295cddadd26cdee4d88068e Mon Sep 17 00:00:00 2001 From: cipchk Date: Sat, 21 Oct 2023 20:52:42 +0800 Subject: [PATCH] feat: add segmented widget --- .../form/widgets/segmented/demo/simple.md | 65 +++++++++++++++++++ .../form/widgets/segmented/index.en-US.md | 22 +++++++ packages/form/widgets/segmented/index.ts | 20 ++++++ .../form/widgets/segmented/index.zh-CN.md | 22 +++++++ .../form/widgets/segmented/ng-package.json | 6 ++ packages/form/widgets/segmented/schema.ts | 21 ++++++ .../form/widgets/segmented/widget.spec.ts | 42 ++++++++++++ packages/form/widgets/segmented/widget.ts | 51 +++++++++++++++ .../shared/json-schema/json-schema.module.ts | 2 + 9 files changed, 251 insertions(+) create mode 100644 packages/form/widgets/segmented/demo/simple.md create mode 100644 packages/form/widgets/segmented/index.en-US.md create mode 100644 packages/form/widgets/segmented/index.ts create mode 100644 packages/form/widgets/segmented/index.zh-CN.md create mode 100644 packages/form/widgets/segmented/ng-package.json create mode 100644 packages/form/widgets/segmented/schema.ts create mode 100644 packages/form/widgets/segmented/widget.spec.ts create mode 100644 packages/form/widgets/segmented/widget.ts diff --git a/packages/form/widgets/segmented/demo/simple.md b/packages/form/widgets/segmented/demo/simple.md new file mode 100644 index 000000000..9afa84984 --- /dev/null +++ b/packages/form/widgets/segmented/demo/simple.md @@ -0,0 +1,65 @@ +--- +title: + zh-CN: 基础样例 + en-US: Basic Usage +order: 0 +--- + +## zh-CN + +最简单的用法。 + +## en-US + +Simplest of usage. + +```ts +import { Component } from '@angular/core'; +import { delay, of } from 'rxjs'; + +import { SFSchema } from '@delon/form'; +import { SFSegmentedWidgetSchema, SegmentedWidget } from '@delon/form/widgets/segmented'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { NzSegmentedOptions } from 'ng-zorro-antd/segmented'; + +@Component({ + selector: 'app-demo', + template: `` +}) +export class DemoComponent { + schema: SFSchema = { + properties: { + base: { + type: 'string', + title: 'Base', + default: 2, + enum: ['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly'], + ui: { + widget: SegmentedWidget.KEY, + valueChange: console.log + } as SFSegmentedWidgetSchema + }, + asyncData: { + type: 'string', + title: 'Async Data', + ui: { + widget: SegmentedWidget.KEY, + asyncData: () => + of([ + { label: 'Label1', value: 'a' }, + { label: 'Label2', value: 'b' }, + { label: 'Label3', value: 'c', disabled: true } + ] as NzSegmentedOptions).pipe(delay(1000)), + valueChange: console.log + } as SFSegmentedWidgetSchema + } + } + }; + + constructor(private msg: NzMessageService) {} + + submit(value: {}): void { + this.msg.success(JSON.stringify(value)); + } +} +``` diff --git a/packages/form/widgets/segmented/index.en-US.md b/packages/form/widgets/segmented/index.en-US.md new file mode 100644 index 000000000..a688d37a5 --- /dev/null +++ b/packages/form/widgets/segmented/index.en-US.md @@ -0,0 +1,22 @@ +--- +title: segmented +subtitle: Segmented +type: Non-built-in widgets +--- + +- When displaying multiple options and user can select a single option; +- When switching the selected option, the content of the associated area changes. + +## Import module + +Non-built-in modules, Should be import `SegmentedWidgetModule` in [json-schema.module.ts](https://github.com/ng-alain/ng-alain/blob/master/src/app/shared/json-schema/json-schema.module.ts#L11). + +## API + +### ui + +| Property | Description | Type | Default | +|----------|-------------|------|---------| +| `[block]` | Option to fit width to its parent\'s width | `boolean` | false | | +| `[asyncData]` | Set children optional | `() => Observable` | - | | +| `(valueChange)` | Emits when index of the currently selected option changes | `(data: { index: number; item: SFValue }) => void` | - | | diff --git a/packages/form/widgets/segmented/index.ts b/packages/form/widgets/segmented/index.ts new file mode 100644 index 000000000..97019ccd8 --- /dev/null +++ b/packages/form/widgets/segmented/index.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +import { DelonFormModule, WidgetRegistry } from '@delon/form'; +import { NzSegmentedModule } from 'ng-zorro-antd/segmented'; + +import { SegmentedWidget } from './widget'; + +export * from './widget'; +export * from './schema'; + +@NgModule({ + imports: [FormsModule, DelonFormModule, NzSegmentedModule], + declarations: [SegmentedWidget] +}) +export class SegmentedWidgetModule { + constructor(widgetRegistry: WidgetRegistry) { + widgetRegistry.register(SegmentedWidget.KEY, SegmentedWidget); + } +} diff --git a/packages/form/widgets/segmented/index.zh-CN.md b/packages/form/widgets/segmented/index.zh-CN.md new file mode 100644 index 000000000..a7c765d64 --- /dev/null +++ b/packages/form/widgets/segmented/index.zh-CN.md @@ -0,0 +1,22 @@ +--- +title: segmented +subtitle: 分段控制器 +type: Non-built-in widgets +--- + +- 用于展示多个选项并允许用户选择其中单个选项; +- 当切换选中选项时,关联区域的内容会发生变化。 + +## 导入模块 + +非内置模块,需要额外在 [json-schema.module.ts](https://github.com/ng-alain/ng-alain/blob/master/src/app/shared/json-schema/json-schema.module.ts#L11) 导入 `SegmentedWidgetModule`。 + +## API + +### ui 属性 + +| 成员 | 说明 | 类型 | 默认值 | +|----|----|----|-----| +| `[block]` | 将宽度调整为父元素宽度的选项 | `boolean` | false | | +| `[asyncData]` | 异步数据 | `() => Observable` | - | | +| `(valueChange)` | 当前选中项目变化时触发回调 | `(data: { index: number; item: SFValue }) => void` | - | | diff --git a/packages/form/widgets/segmented/ng-package.json b/packages/form/widgets/segmented/ng-package.json new file mode 100644 index 000000000..ec57a9fa3 --- /dev/null +++ b/packages/form/widgets/segmented/ng-package.json @@ -0,0 +1,6 @@ +{ + "lib": { + "flatModuleFile": "widgets-color", + "entryFile": "index.ts" + } +} diff --git a/packages/form/widgets/segmented/schema.ts b/packages/form/widgets/segmented/schema.ts new file mode 100644 index 000000000..897c7539b --- /dev/null +++ b/packages/form/widgets/segmented/schema.ts @@ -0,0 +1,21 @@ +import type { TemplateRef } from '@angular/core'; +import type { Observable } from 'rxjs'; + +import type { SFUISchemaItem, SFValue } from '@delon/form'; +import type { NzSegmentedOption, NzSegmentedOptions } from 'ng-zorro-antd/segmented'; + +export interface SFSegmentedWidgetSchema extends SFUISchemaItem { + /** + * 异步数据源 + */ + asyncData?: () => Observable; + /** + * Option to fit width to its parent's width + */ + block?: boolean; + labelTemplate?: TemplateRef<{ $implicit: NzSegmentedOption; index: number }> | null; + /** + * Emits when index of the currently selected option changes + */ + valueChange?: (data: { index: number; item: SFValue }) => void; +} diff --git a/packages/form/widgets/segmented/widget.spec.ts b/packages/form/widgets/segmented/widget.spec.ts new file mode 100644 index 000000000..2a503c199 --- /dev/null +++ b/packages/form/widgets/segmented/widget.spec.ts @@ -0,0 +1,42 @@ +import { DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync } from '@angular/core/testing'; + +import { SFSchema } from '@delon/form'; +import { createTestContext } from '@delon/testing'; + +import { SegmentedWidgetModule, SFSegmentedWidgetSchema } from './index'; +import { configureSFTestSuite, SFPage, TestFormComponent } from '../../spec/base.spec'; + +describe('form: widget: segmented', () => { + let fixture: ComponentFixture; + let dl: DebugElement; + let context: TestFormComponent; + let page: SFPage; + + configureSFTestSuite({ imports: [SegmentedWidgetModule] }); + + beforeEach(() => { + ({ fixture, dl, context } = createTestContext(TestFormComponent)); + page = new SFPage(context.comp); + page.cleanOverlay().prop(dl, context, fixture); + }); + + it('should be working', fakeAsync(() => { + const valueChange = jasmine.createSpy(); + const s: SFSchema = { + properties: { + a: { + type: 'string', + enum: ['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly'], + ui: { + widget: 'segmented', + valueChange + } as SFSegmentedWidgetSchema + } + } + }; + page.newSchema(s).typeEvent('click', '.ant-segmented-item:nth-child(2) .ant-segmented-item-label'); + expect(page.getValue('/a')).toBe(1); + expect(valueChange).toHaveBeenCalled(); + })); +}); diff --git a/packages/form/widgets/segmented/widget.ts b/packages/form/widgets/segmented/widget.ts new file mode 100644 index 000000000..d97ab147b --- /dev/null +++ b/packages/form/widgets/segmented/widget.ts @@ -0,0 +1,51 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +import { ControlUIWidget, SFValue, getData } from '@delon/form'; +import { NzSegmentedOptions } from 'ng-zorro-antd/segmented'; + +import type { SFSegmentedWidgetSchema } from './schema'; + +@Component({ + selector: 'sf-segmented', + template: ` + + `, + preserveWhitespaces: false, + encapsulation: ViewEncapsulation.None +}) +export class SegmentedWidget extends ControlUIWidget { + static readonly KEY = 'segmented'; + private _list?: NzSegmentedOptions; + get list(): NzSegmentedOptions { + return this._list ?? []; + } + + reset(value: SFValue): void { + getData(this.schema, this.ui, value).subscribe(list => { + this._list = list as NzSegmentedOptions; + this.detectChanges(); + }); + } + + valueChange(index: number): void { + if (this.ui.valueChange) { + this.ui.valueChange({ index, item: this.list[index] as SFValue }); + } + } +} diff --git a/src/app/shared/json-schema/json-schema.module.ts b/src/app/shared/json-schema/json-schema.module.ts index 61bb9e095..43932b3b5 100644 --- a/src/app/shared/json-schema/json-schema.module.ts +++ b/src/app/shared/json-schema/json-schema.module.ts @@ -7,6 +7,7 @@ import { ColorWidgetModule } from '@delon/form/widgets/color'; import { MentionWidgetModule } from '@delon/form/widgets/mention'; import { QrCodeWidgetModule } from '@delon/form/widgets/qr-code'; import { RateWidgetModule } from '@delon/form/widgets/rate'; +import { SegmentedWidgetModule } from '@delon/form/widgets/segmented'; import { SliderWidgetModule } from '@delon/form/widgets/slider'; import { TagWidgetModule } from '@delon/form/widgets/tag'; import { TimeWidgetModule } from '@delon/form/widgets/time'; @@ -34,6 +35,7 @@ import { SharedModule } from '../shared.module'; UploadWidgetModule, ColorWidgetModule, QrCodeWidgetModule, + SegmentedWidgetModule, MonacoEditorWidgetModule, TinymceWidgetModule ]