@operato/data-grist 0.2.35 → 0.2.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/custom-elements.json +1702 -5
  3. package/demo/data-grist-test.html +1 -1
  4. package/demo/index.html +18 -1
  5. package/dist/src/data-card/data-card-gutter-menu.js +2 -2
  6. package/dist/src/data-card/data-card-gutter-menu.js.map +1 -1
  7. package/dist/src/data-card/data-card-gutter.d.ts +13 -1
  8. package/dist/src/data-card/data-card-gutter.js +1 -0
  9. package/dist/src/data-card/data-card-gutter.js.map +1 -1
  10. package/dist/src/data-card/data-card.d.ts +1 -1
  11. package/dist/src/data-card/data-card.js +2 -8
  12. package/dist/src/data-card/data-card.js.map +1 -1
  13. package/dist/src/data-consumer.d.ts +1 -10
  14. package/dist/src/data-consumer.js.map +1 -1
  15. package/dist/src/data-grid/data-grid-field.d.ts +2 -2
  16. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  17. package/dist/src/data-grid/data-grid-header.d.ts +3 -0
  18. package/dist/src/data-grid/data-grid-header.js +38 -10
  19. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  20. package/dist/src/data-grist.d.ts +11 -9
  21. package/dist/src/data-grist.js +38 -25
  22. package/dist/src/data-grist.js.map +1 -1
  23. package/dist/src/data-list/data-list-field.d.ts +2 -2
  24. package/dist/src/data-list/data-list-field.js +5 -5
  25. package/dist/src/data-list/data-list-field.js.map +1 -1
  26. package/dist/src/data-list/data-list-gutter.d.ts +13 -1
  27. package/dist/src/data-list/data-list-gutter.js +1 -0
  28. package/dist/src/data-list/data-list-gutter.js.map +1 -1
  29. package/dist/src/data-list/data-list.d.ts +1 -1
  30. package/dist/src/data-list/data-list.js +2 -8
  31. package/dist/src/data-list/data-list.js.map +1 -1
  32. package/dist/src/data-provider.js +2 -2
  33. package/dist/src/data-provider.js.map +1 -1
  34. package/dist/src/data-report/data-report-field.d.ts +2 -2
  35. package/dist/src/data-report/data-report-field.js.map +1 -1
  36. package/dist/src/filters/index.d.ts +2 -0
  37. package/dist/src/filters/index.js +3 -0
  38. package/dist/src/filters/index.js.map +1 -0
  39. package/dist/src/filters/list-select.d.ts +3 -0
  40. package/dist/src/filters/list-select.js +12 -0
  41. package/dist/src/filters/list-select.js.map +1 -0
  42. package/dist/src/filters/registry.d.ts +7 -0
  43. package/dist/src/filters/registry.js +40 -0
  44. package/dist/src/filters/registry.js.map +1 -0
  45. package/dist/src/index.d.ts +2 -0
  46. package/dist/src/index.js +2 -0
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/interfaces/index.d.ts +2 -0
  49. package/dist/src/interfaces/index.js +3 -0
  50. package/dist/src/interfaces/index.js.map +1 -0
  51. package/dist/src/interfaces/ox-grist-search-form.d.ts +6 -0
  52. package/dist/src/interfaces/ox-grist-search-form.js +2 -0
  53. package/dist/src/interfaces/ox-grist-search-form.js.map +1 -0
  54. package/dist/src/interfaces/ox-search-field.d.ts +39 -0
  55. package/dist/src/interfaces/ox-search-field.js +2 -0
  56. package/dist/src/interfaces/ox-search-field.js.map +1 -0
  57. package/dist/src/search-form/index.d.ts +7 -0
  58. package/dist/src/search-form/index.js +8 -0
  59. package/dist/src/search-form/index.js.map +1 -0
  60. package/dist/src/search-form/ox-basic-field.d.ts +18 -0
  61. package/dist/src/search-form/ox-basic-field.js +75 -0
  62. package/dist/src/search-form/ox-basic-field.js.map +1 -0
  63. package/dist/src/search-form/ox-checkbox-field.d.ts +11 -0
  64. package/dist/src/search-form/ox-checkbox-field.js +60 -0
  65. package/dist/src/search-form/ox-checkbox-field.js.map +1 -0
  66. package/dist/src/search-form/ox-grist-search-form.d.ts +11 -0
  67. package/dist/src/search-form/ox-grist-search-form.js +177 -0
  68. package/dist/src/search-form/ox-grist-search-form.js.map +1 -0
  69. package/dist/src/search-form/ox-number-field.d.ts +14 -0
  70. package/dist/src/search-form/ox-number-field.js +112 -0
  71. package/dist/src/search-form/ox-number-field.js.map +1 -0
  72. package/dist/src/search-form/ox-search-form.d.ts +15 -0
  73. package/dist/src/search-form/ox-search-form.js +53 -0
  74. package/dist/src/search-form/ox-search-form.js.map +1 -0
  75. package/dist/src/search-form/ox-select-field.d.ts +21 -0
  76. package/dist/src/search-form/ox-select-field.js +181 -0
  77. package/dist/src/search-form/ox-select-field.js.map +1 -0
  78. package/dist/src/search-form/ox-text-field.d.ts +11 -0
  79. package/dist/src/search-form/ox-text-field.js +60 -0
  80. package/dist/src/search-form/ox-text-field.js.map +1 -0
  81. package/dist/src/types.d.ts +32 -3
  82. package/dist/src/types.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +7 -6
  85. package/src/data-card/data-card-gutter-menu.ts +2 -2
  86. package/src/data-card/data-card-gutter.ts +3 -3
  87. package/src/data-card/data-card.ts +3 -9
  88. package/src/data-consumer.ts +1 -6
  89. package/src/data-grid/data-grid-field.ts +2 -2
  90. package/src/data-grid/data-grid-header.ts +42 -11
  91. package/src/data-grist.ts +52 -31
  92. package/src/data-list/data-list-field.ts +8 -7
  93. package/src/data-list/data-list-gutter.ts +3 -3
  94. package/src/data-list/data-list.ts +3 -9
  95. package/src/data-provider.ts +4 -5
  96. package/src/data-report/data-report-field.ts +2 -2
  97. package/src/filters/index.ts +3 -0
  98. package/src/filters/list-select.ts +14 -0
  99. package/src/filters/registry.ts +48 -0
  100. package/src/index.ts +3 -0
  101. package/src/interfaces/index.ts +2 -0
  102. package/src/interfaces/ox-grist-search-form.ts +7 -0
  103. package/src/interfaces/ox-search-field.ts +52 -0
  104. package/src/search-form/index.ts +7 -0
  105. package/src/search-form/ox-basic-field.ts +86 -0
  106. package/src/search-form/ox-checkbox-field.ts +57 -0
  107. package/src/search-form/ox-grist-search-form.ts +200 -0
  108. package/src/search-form/ox-number-field.ts +113 -0
  109. package/src/search-form/ox-search-form.ts +71 -0
  110. package/src/search-form/ox-select-field.ts +188 -0
  111. package/src/search-form/ox-text-field.ts +55 -0
  112. package/src/types.ts +37 -3
  113. package/yarn-error.log +2427 -3205
@@ -0,0 +1,200 @@
1
+ import { ColumnConfig, GristConfig } from '../types'
2
+ import { LitElement, TemplateResult, html } from 'lit'
3
+ import {
4
+ OXBasicFieldProps,
5
+ OXCheckboxFieldProps,
6
+ OXNumberFieldProps,
7
+ OXSearchFieldProps,
8
+ OXSearchFieldTypes,
9
+ OXSearchForm,
10
+ OXSelectFieldProps,
11
+ OXTextFieldProps,
12
+ QueryFilter
13
+ } from '..'
14
+ import { customElement, property } from 'lit/decorators.js'
15
+
16
+ @customElement('ox-grist-search-form')
17
+ export class OXGristSearchForm extends LitElement {
18
+ @property({ type: Object }) config!: GristConfig
19
+ @property({ type: String }) defaultOperator: string = 'eq'
20
+
21
+ private timeout?: NodeJS.Timeout
22
+
23
+ render(): TemplateResult {
24
+ const fields: OXSearchFieldProps[] = this.convertToSearchFields(this.config)
25
+
26
+ return html` <ox-search-form .fields=${fields}></ox-search-form> `
27
+ }
28
+
29
+ private convertToSearchFields(config: GristConfig): OXSearchFieldProps[] {
30
+ const supportingTypes: string[] = ['string', 'integer', 'number', 'float', 'select', 'boolean', 'checkbox']
31
+
32
+ return config.columns
33
+ .filter((columnConfig: ColumnConfig) => supportingTypes.indexOf(columnConfig.type) >= 0)
34
+ .map((columnConfig: ColumnConfig) => {
35
+ let fieldProps: OXBasicFieldProps = {
36
+ name: columnConfig.name,
37
+ hidden: Boolean(columnConfig.hidden),
38
+ placeholder: typeof columnConfig.label === 'string' ? columnConfig.label : columnConfig.name
39
+ }
40
+
41
+ switch (columnConfig.type) {
42
+ case 'string':
43
+ return {
44
+ ...fieldProps,
45
+ type: 'text'
46
+ } as OXTextFieldProps
47
+
48
+ case 'integer':
49
+ return {
50
+ ...fieldProps,
51
+ type: 'number',
52
+ min: columnConfig.record?.options?.min || undefined,
53
+ max: columnConfig.record?.options?.max || undefined
54
+ } as OXNumberFieldProps
55
+
56
+ case 'number':
57
+ return {
58
+ ...fieldProps,
59
+ type: 'number',
60
+ min: columnConfig.record?.options?.min || undefined,
61
+ max: columnConfig.record?.options?.max || undefined
62
+ } as OXNumberFieldProps
63
+
64
+ case 'float':
65
+ return {
66
+ ...fieldProps,
67
+ type: 'number',
68
+ min: columnConfig.record?.options?.min || undefined,
69
+ max: columnConfig.record?.options?.max || undefined
70
+ } as OXNumberFieldProps
71
+
72
+ case 'select':
73
+ return {
74
+ ...fieldProps,
75
+ type: 'select',
76
+ searchEnable: true,
77
+ options: columnConfig.record?.options.map((option: string | Record<string, any>) => {
78
+ if (typeof option === 'string') {
79
+ return { value: option }
80
+ } else if (option.display && option.value) {
81
+ return {
82
+ name: option.display,
83
+ value: option.value
84
+ }
85
+ } else {
86
+ throw new Error('Unexpected option property')
87
+ }
88
+ })
89
+ } as OXSelectFieldProps
90
+
91
+ case 'boolean':
92
+ return {
93
+ ...fieldProps,
94
+ type: 'checkbox',
95
+ indeterminate: true
96
+ } as OXCheckboxFieldProps
97
+
98
+ case 'checkbox':
99
+ return {
100
+ ...fieldProps,
101
+ type: 'checkbox',
102
+ indeterminate: true
103
+ } as OXCheckboxFieldProps
104
+
105
+ default:
106
+ throw new Error('Non-supported type of column config')
107
+ }
108
+ })
109
+
110
+ // return searchFields
111
+ // .filter((searchField: string) => fieldMap.has(searchField))
112
+ // .map((searchField: string) => {
113
+ // const columnConfig: ColumnConfig | undefined = fieldMap.get(searchField)
114
+ // if (!columnConfig) throw new Error('No matched column config')
115
+
116
+ // let fieldProps: OXBasicFieldProps = {
117
+ // name: columnConfig.name,
118
+ // hidden: Boolean(columnConfig.hidden),
119
+ // label: typeof columnConfig.label === 'string' ? columnConfig.label : undefined
120
+ // }
121
+
122
+ // switch (columnConfig.type) {
123
+ // case 'string':
124
+ // return {
125
+ // ...fieldProps,
126
+ // type: 'text'
127
+ // } as OXTextFieldProps
128
+
129
+ // case 'integer':
130
+ // return {
131
+ // ...fieldProps,
132
+ // type: 'number',
133
+ // min: columnConfig.record?.options?.min || undefined,
134
+ // max: columnConfig.record?.options?.max || undefined
135
+ // } as OXNumberFieldProps
136
+
137
+ // case 'number':
138
+ // return {
139
+ // ...fieldProps,
140
+ // type: 'number',
141
+ // min: columnConfig.record?.options?.min || undefined,
142
+ // max: columnConfig.record?.options?.max || undefined
143
+ // } as OXNumberFieldProps
144
+
145
+ // case 'float':
146
+ // return {
147
+ // ...fieldProps,
148
+ // type: 'number',
149
+ // min: columnConfig.record?.options?.min || undefined,
150
+ // max: columnConfig.record?.options?.max || undefined
151
+ // } as OXNumberFieldProps
152
+
153
+ // case 'select':
154
+ // return {
155
+ // ...fieldProps,
156
+ // type: 'select',
157
+ // searchEnable: true,
158
+ // options: columnConfig.record?.options.map((option: Record<string, any>) => ({
159
+ // name: option.display,
160
+ // value: option.value
161
+ // }))
162
+ // } as OXSelectFieldProps
163
+
164
+ // case 'boolean':
165
+ // return {
166
+ // ...fieldProps,
167
+ // type: 'checkbox',
168
+ // indeterminate: true
169
+ // } as OXCheckboxFieldProps
170
+
171
+ // case 'checkbox':
172
+ // return {
173
+ // ...fieldProps,
174
+ // type: 'checkbox',
175
+ // indeterminate: true
176
+ // } as OXCheckboxFieldProps
177
+
178
+ // default:
179
+ // throw new Error('Non-supported type of column config')
180
+ // }
181
+ // })
182
+ }
183
+
184
+ get queryFilters(): QueryFilter[] {
185
+ const searchForm: OXSearchForm | null = this.renderRoot.querySelector<OXSearchForm>('ox-search-form')
186
+ if (!searchForm) throw new Error('Failed to find search form')
187
+
188
+ const searchFields: OXSearchFieldTypes[] = searchForm.searchFields
189
+
190
+ return searchFields
191
+ .filter(({ value }: OXSearchFieldTypes) => value !== null && value !== undefined && value !== '')
192
+ .map((searchField: OXSearchFieldTypes) => {
193
+ return {
194
+ name: searchField.field.name,
195
+ operator: searchField.field.operator || this.defaultOperator,
196
+ value: searchField.value
197
+ }
198
+ })
199
+ }
200
+ }
@@ -0,0 +1,113 @@
1
+ import '@material/mwc-icon'
2
+
3
+ import { css, CSSResult, html, TemplateResult } from 'lit'
4
+ import { ifDefined } from 'lit-html/directives/if-defined.js'
5
+ import { customElement, property } from 'lit/decorators.js'
6
+
7
+ import { OXNumberFieldProps } from '..'
8
+ import { OXBasicField } from './ox-basic-field'
9
+
10
+ @customElement('ox-number-field')
11
+ export class OXNumberField extends OXBasicField {
12
+ @property({ type: Object }) field!: OXNumberFieldProps
13
+ @property({ type: Number }) value?: number
14
+
15
+ setDefaultValue(defaultValue: number): void {
16
+ this.value = defaultValue
17
+ }
18
+
19
+ static styles: CSSResult[] = [
20
+ css`
21
+ :host {
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ }
25
+ input::-webkit-outer-spin-button,
26
+ input::-webkit-inner-spin-button {
27
+ -webkit-appearance: none;
28
+ margin: 0;
29
+ }
30
+
31
+ span.input-wrapper {
32
+ display: inline-flex;
33
+ }
34
+
35
+ button {
36
+ padding: var(--padding-narrow);
37
+ height: 24px;
38
+ background-color: var(--theme-white-color);
39
+ border: 1px solid rgba(var(--primary-color-rgb), 0.5);
40
+ border-radius: 50%;
41
+ opacity: 0.6;
42
+ cursor: pointer;
43
+ }
44
+ button mwc-icon {
45
+ font-size: var(--fontsize-default, 16px);
46
+ color: var(--primary-text-color, #476172);
47
+ }
48
+ button:hover {
49
+ opacity: 1;
50
+ }
51
+ input {
52
+ border: none;
53
+ outline: none;
54
+ padding: 4px 9px;
55
+ text-align: center;
56
+ font-size: var(--fontsize-default, 14px);
57
+ color: var(--primary-text-color, #476172);
58
+ }
59
+ `
60
+ ]
61
+
62
+ render(): TemplateResult {
63
+ const { name, hidden, id, placeholder, min, max, step } = this.field
64
+
65
+ return html`
66
+ <span class="input-wrapper" ?hidden=${hidden}>
67
+ <button @click=${this.decrease.bind(this)}>
68
+ <mwc-icon>add</mwc-icon>
69
+ </button>
70
+ <input
71
+ id=${ifDefined(id)}
72
+ name=${name}
73
+ type="number"
74
+ placeholder=${ifDefined(placeholder)}
75
+ min=${ifDefined(min)}
76
+ max=${ifDefined(max)}
77
+ step=${ifDefined(step)}
78
+ .value=${String(this.value) || ''}
79
+ @change=${this.onChangeHandler.bind(this)}
80
+ />
81
+ <button @click=${this.increase.bind(this)}>
82
+ <mwc-icon>remove</mwc-icon>
83
+ </button>
84
+ </span>
85
+ `
86
+ }
87
+
88
+ decrease(): void {
89
+ let fractionDigits: number = 0
90
+ if (this.field.step) {
91
+ fractionDigits = String(this.field.step).split('.')[1].length
92
+ }
93
+
94
+ this.value = Number(((this.value ?? 0) - (this.field.step || 1)).toFixed(fractionDigits))
95
+ }
96
+
97
+ increase(): void {
98
+ let fractionDigits: number = 0
99
+ if (this.field.step) {
100
+ fractionDigits = String(this.field.step).split('.')[1].length
101
+ }
102
+
103
+ this.value = Number(((this.value ?? 0) + (this.field.step || 1)).toFixed(fractionDigits))
104
+ }
105
+
106
+ private onChangeHandler(): void {
107
+ if (this.input.value) {
108
+ this.value = Number(this.input.value)
109
+ } else {
110
+ this.value = undefined
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,71 @@
1
+ import './ox-text-field'
2
+ import './ox-checkbox-field'
3
+ import './ox-select-field'
4
+ import './ox-number-field'
5
+
6
+ import { LitElement, TemplateResult, html } from 'lit'
7
+ import {
8
+ OXCheckboxField,
9
+ OXCheckboxFieldProps,
10
+ OXNumberField,
11
+ OXNumberFieldProps,
12
+ OXSearchFieldProps,
13
+ OXSelectField,
14
+ OXSelectFieldProps,
15
+ OXTextField,
16
+ OXTextFieldProps
17
+ } from '..'
18
+ import { customElement, property, query, queryAll } from 'lit/decorators.js'
19
+
20
+ export type OXSearchFieldTypes = OXTextField | OXNumberField | OXSelectField | OXCheckboxField
21
+
22
+ @customElement('ox-search-form')
23
+ export class OXSearchForm extends LitElement {
24
+ @property({ type: Array }) fields!: OXSearchFieldProps[]
25
+
26
+ @query('form') form!: HTMLFormElement
27
+
28
+ render(): TemplateResult {
29
+ return html`
30
+ <form @submit-field=${this._submitFieldHandler.bind(this)}>
31
+ <slot></slot>
32
+ ${this.fields.map(this.renderField)}
33
+ </form>
34
+ `
35
+ }
36
+
37
+ get searchFields(): OXSearchFieldTypes[] {
38
+ return Array.from(this.form.querySelectorAll('*'))
39
+ }
40
+
41
+ private renderField(field: OXSearchFieldProps): TemplateResult {
42
+ const { type } = field
43
+
44
+ switch (type) {
45
+ case 'text':
46
+ return html`<ox-text-field .field=${field as OXTextFieldProps}></ox-text-field>`
47
+
48
+ case 'number':
49
+ return html`<ox-number-field .field=${field as OXNumberFieldProps}></ox-number-field>`
50
+
51
+ case 'select':
52
+ return html`<ox-select-field .field=${field as OXSelectFieldProps}></ox-select-field>`
53
+
54
+ case 'checkbox':
55
+ return html`<ox-checkbox-field .field=${field as OXCheckboxFieldProps}></ox-checkbox-field>`
56
+
57
+ default:
58
+ return html`<ox-text-field .field=${field as OXTextFieldProps}></ox-text-field>`
59
+ }
60
+ }
61
+
62
+ _submitFieldHandler(): void {
63
+ this.dispatchEvent(
64
+ new CustomEvent('submit', {
65
+ composed: true,
66
+ bubbles: true,
67
+ cancelable: true
68
+ })
69
+ ) && this.form.submit()
70
+ }
71
+ }
@@ -0,0 +1,188 @@
1
+ import '@operato/popup'
2
+ import '@material/mwc-icon'
3
+
4
+ import { CSSResult, TemplateResult, css, html } from 'lit'
5
+ import { OXFieldOptionProps, OXSelectFieldProps } from '..'
6
+ import { customElement, property } from 'lit/decorators.js'
7
+
8
+ import { OXBasicField } from './ox-basic-field'
9
+ import { OxPopupList } from '@operato/popup'
10
+ import { ifDefined } from 'lit-html/directives/if-defined.js'
11
+
12
+ @customElement('ox-select-field')
13
+ export class OXSelectField extends OXBasicField {
14
+ @property({ type: Object }) field!: OXSelectFieldProps
15
+ @property({ type: Boolean }) checked: boolean = false
16
+ @property({ type: String }) searchCondition: string = ''
17
+ @property({ type: String }) value: string = ''
18
+
19
+ static styles: CSSResult[] = [
20
+ css`
21
+ :host > label {
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ font-size: var(--fontsize-default, 14px);
25
+ color: var(--primary-text-color, #476172);
26
+ }
27
+ .input-wrapper {
28
+ display: inline-flex;
29
+ }
30
+ .input-wrapper mwc-icon {
31
+ opacity: 0.7;
32
+ font-size: var(--fontsize-default, 16px);
33
+ color: var(--primary-text-color, #476172);
34
+ line-height: 2;
35
+ }
36
+ input {
37
+ border: none;
38
+ outline: none;
39
+ padding: 4px 9px;
40
+ font-size: var(--fontsize-default, 14px);
41
+ color: var(--primary-text-color, #476172);
42
+ }
43
+ .search-input-wrapper {
44
+ display: inline-flex;
45
+ border-bottom: var(--border-dark-color, 1px solid rgba(0, 0, 0, 0.15));
46
+ }
47
+ .search-input-wrapper mwc-icon {
48
+ opacity: 0.7;
49
+ font-size: 20px;
50
+ color: var(--primary-text-color, #476172);
51
+ }
52
+ label[option] {
53
+ display: inline-flex;
54
+ margin: 5px 0px;
55
+ gap: 5px;
56
+ }
57
+ `
58
+ ]
59
+
60
+ get input(): HTMLInputElement {
61
+ const input: HTMLInputElement | null = this.renderRoot.querySelector('input[readonly]')
62
+ if (!input) throw new Error('Failed to find input element')
63
+
64
+ return input
65
+ }
66
+
67
+ get checkedOption(): OXFieldOptionProps {
68
+ if (!this.value) throw new Error('Value is not defined yet')
69
+
70
+ const checkedOption: OXFieldOptionProps | undefined = this.field.options.find(
71
+ (option: OXFieldOptionProps) => option.value === this.value
72
+ )
73
+ if (!checkedOption) throw new Error('No checked option found')
74
+
75
+ return checkedOption
76
+ }
77
+
78
+ get displayValue(): string {
79
+ try {
80
+ return this.checkedOption.name || this.checkedOption.value
81
+ } catch (e) {
82
+ return ''
83
+ }
84
+ }
85
+
86
+ get searchInput(): HTMLInputElement {
87
+ const searchInput: HTMLInputElement | null = this.renderRoot.querySelector('input#search-input')
88
+ if (!searchInput) throw new Error('No search input found')
89
+
90
+ return searchInput
91
+ }
92
+
93
+ setDefaultValue(defaultValue: any): void {
94
+ const input: HTMLInputElement | null = this.renderRoot.querySelector(`input[value="${defaultValue}"]`)
95
+ if (!input) return
96
+ input.checked = true
97
+
98
+ const option: OXFieldOptionProps | undefined = this.field.options.find(
99
+ (option: OXFieldOptionProps) => option.value === defaultValue
100
+ )
101
+ if (!option) throw new Error('No matched option found')
102
+
103
+ this.value = option.name || option.value
104
+ }
105
+
106
+ render(): TemplateResult {
107
+ const { name, hidden, id, options, searchEnable = true, placeholder } = this.field
108
+
109
+ return html`
110
+ <span class="input-wrapper" ?hidden=${hidden} @click=${this.openPopup.bind(this)}>
111
+ <input
112
+ id=${ifDefined(id)}
113
+ name=${name}
114
+ readonly
115
+ placeholder=${ifDefined(placeholder)}
116
+ .value=${this.displayValue}
117
+ />
118
+ <mwc-icon>keyboard_arrow_down</mwc-icon>
119
+ </span>
120
+
121
+ <ox-popup-list id="popup-list" multiple>
122
+ ${searchEnable
123
+ ? html`
124
+ <div class="search-input-wrapper">
125
+ <input id="search-input" type="text" @input=${this.onSearchInputHandler.bind(this)} />
126
+ <mwc-icon>search</mwc-icon>
127
+ </div>
128
+ `
129
+ : ''}
130
+ ${options
131
+ .filter(
132
+ (option: OXFieldOptionProps) =>
133
+ option.value.indexOf(this.searchCondition) >= 0 ||
134
+ (option.name && option?.name.indexOf(this.searchCondition) >= 0)
135
+ )
136
+ .map(
137
+ (option: OXFieldOptionProps) => html`
138
+ <label option>
139
+ <input
140
+ name=${name}
141
+ type="checkbox"
142
+ .value=${option.value}
143
+ @change=${this.onValueChange.bind(this)}
144
+ .checked=${this.value === option.value}
145
+ />
146
+ <span>${option.name || option.value}</span>
147
+ </label>
148
+ `
149
+ )}
150
+ </ox-popup-list>
151
+ `
152
+ }
153
+
154
+ openPopup(event: MouseEvent): void {
155
+ const popupList: OxPopupList | null = this.renderRoot.querySelector('#popup-list')
156
+ if (!popupList) throw new Error('Failed to find popup element')
157
+
158
+ const offsetHeight: number = this.input.offsetHeight
159
+ const { x, y } = this.input.getBoundingClientRect()
160
+
161
+ popupList.open({ left: x, top: y + offsetHeight })
162
+ }
163
+
164
+ onSearchInputHandler(): void {
165
+ this.searchCondition = this.searchInput.value
166
+ }
167
+
168
+ onValueChange(event: Event): void {
169
+ if (this.value) {
170
+ const prevCheckedInput: HTMLInputElement | null = this.renderRoot.querySelector(`input[value=${this.value}`)
171
+ if (prevCheckedInput) prevCheckedInput.checked = false
172
+ }
173
+
174
+ const checkedInput: HTMLInputElement | null = event.currentTarget as HTMLInputElement | null
175
+ if (!checkedInput) throw new Error('No checked input found')
176
+
177
+ if (checkedInput.value === this.value) {
178
+ this.value = ''
179
+ } else {
180
+ const foundOption: OXFieldOptionProps | undefined = this.field.options.find(
181
+ (option: OXFieldOptionProps) => option.value === checkedInput.value
182
+ )
183
+ if (!foundOption) throw new Error('No matched option found')
184
+
185
+ this.value = foundOption.value
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,55 @@
1
+ import { css, html, TemplateResult } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+ import { ifDefined } from 'lit/directives/if-defined.js'
4
+
5
+ import { OXTextFieldProps } from '..'
6
+ import { OXBasicField } from './ox-basic-field'
7
+
8
+ @customElement('ox-text-field')
9
+ export class OXTextField extends OXBasicField {
10
+ @property({ type: Object }) field!: OXTextFieldProps
11
+ @property({ type: String }) value: string = ''
12
+
13
+ static styles = [
14
+ ...OXBasicField.styles,
15
+ css`
16
+ label {
17
+ display: inline-flex;
18
+ flex-direction: column;
19
+ }
20
+ span.input-wrapper {
21
+ display: inline-flex;
22
+ border-bottom: var(--border-dark-color, 1px solid rgba(0, 0, 0, 0.15));
23
+ }
24
+ `
25
+ ]
26
+
27
+ setDefaultValue(defaultValue: string) {
28
+ this.value = defaultValue
29
+ }
30
+
31
+ render(): TemplateResult {
32
+ if (!this.field) return html``
33
+
34
+ const { name, hidden, id, placeholder } = this.field
35
+
36
+ return html`
37
+ <label>
38
+ <span class="input-wrapper" ?hidden=${hidden}>
39
+ <input
40
+ id=${ifDefined(id)}
41
+ name=${name}
42
+ type="text"
43
+ placeholder=${ifDefined(placeholder)}
44
+ .value=${this.value}
45
+ @input=${this.onChangeHandler.bind(this)}
46
+ />
47
+ </span>
48
+ </label>
49
+ `
50
+ }
51
+
52
+ private onChangeHandler(): void {
53
+ this.value = this.input.value
54
+ }
55
+ }
package/src/types.ts CHANGED
@@ -1,3 +1,14 @@
1
+ import { TemplateResult } from 'lit-html'
2
+
3
+ import { DataCardField } from './data-card/data-card-field'
4
+ import { DataCardGutter } from './data-card/data-card-gutter'
5
+ import { RecordCard } from './data-card/record-card'
6
+ import { DataGridField } from './data-grid/data-grid-field'
7
+ import { DataListField } from './data-list/data-list-field'
8
+ import { DataListGutter } from './data-list/data-list-gutter'
9
+ import { RecordPartial } from './data-list/record-partial'
10
+ import { DataReportField } from './data-report/data-report-field'
11
+
1
12
  export type GristConfig = {
2
13
  columns: ColumnConfig[]
3
14
  rows: RowsConfig
@@ -8,6 +19,7 @@ export type GristConfig = {
8
19
 
9
20
  export type SorterConfig = { name: string; desc?: boolean }
10
21
  export type SortersConfig = SorterConfig[]
22
+ export type FilterConfig = { type: string; options?: { [key: string]: any } }
11
23
 
12
24
  export type PaginationConfig = {
13
25
  page?: number
@@ -16,6 +28,14 @@ export type PaginationConfig = {
16
28
  infinite?: boolean
17
29
  }
18
30
 
31
+ export type FetchOption = { page?: number; limit?: number; sorters?: object[]; options?: object }
32
+ export type FetchHandler = (param: FetchOption) => {
33
+ page?: number
34
+ limit?: number
35
+ total: number
36
+ records: object[]
37
+ }
38
+
19
39
  export type GristEventHandler = (
20
40
  columns: ColumnConfig[],
21
41
  data?: GristData,
@@ -39,6 +59,7 @@ export type ColumnConfig = {
39
59
  width?: number | string | ColumnWidthCallback
40
60
  forList?: boolean
41
61
  validation?: ValidationCallback
62
+ filter?: FilterConfig
42
63
  imex?: ImexConfig
43
64
  multiple?: boolean
44
65
  }
@@ -71,9 +92,22 @@ export type RecordConfig = {
71
92
  rowOptionField?: string
72
93
  }
73
94
 
74
- export type FieldRenderer = (value: any, column: ColumnConfig, record: GristRecord, rowIndex: number, owner: any) => any
75
- export type FieldEditor = (value: any, column: ColumnConfig, record: GristRecord, rowIndex: number, owner: any) => any
76
- export type FieldThumbnailRenderer = (record: GristRecord, rowIndex: number) => any
95
+ export type FieldRenderer = (
96
+ value: any,
97
+ column: ColumnConfig,
98
+ record: GristRecord,
99
+ rowIndex: number,
100
+ owner: RecordCard | DataCardGutter | DataCardField | DataListGutter | DataListField | RecordPartial | DataReportField
101
+ ) => TemplateResult | string | void
102
+ export type FieldEditor = (
103
+ value: any,
104
+ column: ColumnConfig,
105
+ record: GristRecord,
106
+ rowIndex: number,
107
+ field: DataGridField
108
+ ) => Element
109
+ export type FieldThumbnailRenderer = (record: GristRecord, rowIndex: number) => TemplateResult | string | void
110
+ export type FilterSelectRenderer = (column: ColumnConfig, owner: Element) => TemplateResult | string | void
77
111
 
78
112
  export type GristEventHandlerSet = {
79
113
  click?: GristEventHandler