@operato/data-grist 2.0.0-alpha.99 → 2.0.0-beta.1

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 (216) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/demo/data-grist-test.html +1 -1
  3. package/demo/index.html +1 -1
  4. package/dist/src/data-card/data-card-field.js +2 -2
  5. package/dist/src/data-card/data-card-field.js.map +1 -1
  6. package/dist/src/data-card/data-card-gutter-menu.js +5 -5
  7. package/dist/src/data-card/data-card-gutter-menu.js.map +1 -1
  8. package/dist/src/data-card/data-card-gutter.js +6 -6
  9. package/dist/src/data-card/data-card-gutter.js.map +1 -1
  10. package/dist/src/data-card/data-card.js +7 -9
  11. package/dist/src/data-card/data-card.js.map +1 -1
  12. package/dist/src/data-card/record-card.js +9 -10
  13. package/dist/src/data-card/record-card.js.map +1 -1
  14. package/dist/src/data-grid/data-grid-accum-field.js +12 -5
  15. package/dist/src/data-grid/data-grid-accum-field.js.map +1 -1
  16. package/dist/src/data-grid/data-grid-body-style.js +12 -0
  17. package/dist/src/data-grid/data-grid-body-style.js.map +1 -1
  18. package/dist/src/data-grid/data-grid-body.d.ts +0 -1
  19. package/dist/src/data-grid/data-grid-body.js +14 -21
  20. package/dist/src/data-grid/data-grid-body.js.map +1 -1
  21. package/dist/src/data-grid/data-grid-field.js +8 -2
  22. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  23. package/dist/src/data-grid/data-grid-footer.js +4 -2
  24. package/dist/src/data-grid/data-grid-footer.js.map +1 -1
  25. package/dist/src/data-grid/data-grid-header.js +9 -6
  26. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  27. package/dist/src/data-grid/data-grid.js +23 -1
  28. package/dist/src/data-grid/data-grid.js.map +1 -1
  29. package/dist/src/data-grid/event-handlers/data-grid-body-click-handler.js +3 -0
  30. package/dist/src/data-grid/event-handlers/data-grid-body-click-handler.js.map +1 -1
  31. package/dist/src/data-grist.d.ts +10 -2
  32. package/dist/src/data-grist.js +71 -8
  33. package/dist/src/data-grist.js.map +1 -1
  34. package/dist/src/data-list/data-list-field.js +5 -5
  35. package/dist/src/data-list/data-list-field.js.map +1 -1
  36. package/dist/src/data-list/data-list-gutter.js +3 -3
  37. package/dist/src/data-list/data-list-gutter.js.map +1 -1
  38. package/dist/src/data-list/data-list.js +4 -4
  39. package/dist/src/data-list/data-list.js.map +1 -1
  40. package/dist/src/data-list/record-partial.js +9 -10
  41. package/dist/src/data-list/record-partial.js.map +1 -1
  42. package/dist/src/data-manipulator.d.ts +1 -1
  43. package/dist/src/data-manipulator.js +5 -5
  44. package/dist/src/data-manipulator.js.map +1 -1
  45. package/dist/src/data-report/data-report-field.js +2 -1
  46. package/dist/src/data-report/data-report-field.js.map +1 -1
  47. package/dist/src/data-report/data-report-header.js +2 -2
  48. package/dist/src/data-report/data-report-header.js.map +1 -1
  49. package/dist/src/editors/ox-grist-editor-select.js +37 -25
  50. package/dist/src/editors/ox-grist-editor-select.js.map +1 -1
  51. package/dist/src/editors/ox-input-tree.js +8 -8
  52. package/dist/src/editors/ox-input-tree.js.map +1 -1
  53. package/dist/src/filters/filter-input-barcode.js +1 -0
  54. package/dist/src/filters/filter-input-barcode.js.map +1 -1
  55. package/dist/src/filters/filter-select.js +30 -16
  56. package/dist/src/filters/filter-select.js.map +1 -1
  57. package/dist/src/filters/filter-styles.js +46 -31
  58. package/dist/src/filters/filter-styles.js.map +1 -1
  59. package/dist/src/filters/filters-form.d.ts +15 -4
  60. package/dist/src/filters/filters-form.js +205 -70
  61. package/dist/src/filters/filters-form.js.map +1 -1
  62. package/dist/src/gutters/gutter-dirty.js +2 -2
  63. package/dist/src/gutters/gutter-dirty.js.map +1 -1
  64. package/dist/src/index.d.ts +1 -0
  65. package/dist/src/index.js +1 -0
  66. package/dist/src/index.js.map +1 -1
  67. package/dist/src/personalizer/index.d.ts +1 -0
  68. package/dist/src/personalizer/index.js +2 -0
  69. package/dist/src/personalizer/index.js.map +1 -0
  70. package/dist/src/personalizer/ox-grist-filter-personalizer.d.ts +8 -0
  71. package/dist/src/personalizer/ox-grist-filter-personalizer.js +177 -0
  72. package/dist/src/personalizer/ox-grist-filter-personalizer.js.map +1 -0
  73. package/dist/src/personalizer/ox-grist-personalizer.d.ts +8 -0
  74. package/dist/src/personalizer/ox-grist-personalizer.js +178 -0
  75. package/dist/src/personalizer/ox-grist-personalizer.js.map +1 -0
  76. package/dist/src/record-view/record-creator.js +2 -2
  77. package/dist/src/record-view/record-creator.js.map +1 -1
  78. package/dist/src/renderers/ox-grist-renderer-select.js +34 -4
  79. package/dist/src/renderers/ox-grist-renderer-select.js.map +1 -1
  80. package/dist/src/renderers/ox-grist-renderer-tree.js +8 -8
  81. package/dist/src/renderers/ox-grist-renderer-tree.js.map +1 -1
  82. package/dist/src/sorters/sorters-control.js +3 -3
  83. package/dist/src/sorters/sorters-control.js.map +1 -1
  84. package/dist/src/types.d.ts +41 -2
  85. package/dist/src/types.js.map +1 -1
  86. package/dist/stories/{accumulator.stories.d.ts → accumulator-format.stories.d.ts} +9 -0
  87. package/dist/stories/{accumulator.stories.js → accumulator-format.stories.js} +24 -12
  88. package/dist/stories/accumulator-format.stories.js.map +1 -0
  89. package/dist/stories/barcode-input-filter.stories.d.ts +5 -0
  90. package/dist/stories/barcode-input-filter.stories.js +29 -5
  91. package/dist/stories/barcode-input-filter.stories.js.map +1 -1
  92. package/dist/stories/bounded-select-filters.stories.d.ts +30 -0
  93. package/dist/stories/bounded-select-filters.stories.js +288 -0
  94. package/dist/stories/bounded-select-filters.stories.js.map +1 -0
  95. package/dist/stories/bounded-select-record.stories.d.ts +30 -0
  96. package/dist/stories/bounded-select-record.stories.js +291 -0
  97. package/dist/stories/bounded-select-record.stories.js.map +1 -0
  98. package/dist/stories/click-event.stories.d.ts +41 -0
  99. package/dist/stories/click-event.stories.js +234 -0
  100. package/dist/stories/click-event.stories.js.map +1 -0
  101. package/dist/stories/creatable-only-column.stories.d.ts +5 -0
  102. package/dist/stories/creatable-only-column.stories.js +46 -21
  103. package/dist/stories/creatable-only-column.stories.js.map +1 -1
  104. package/dist/stories/default-filters.stories.d.ts +5 -0
  105. package/dist/stories/default-filters.stories.js +84 -17
  106. package/dist/stories/default-filters.stories.js.map +1 -1
  107. package/dist/stories/dynamic-editable.stories.d.ts +5 -0
  108. package/dist/stories/dynamic-editable.stories.js +44 -21
  109. package/dist/stories/dynamic-editable.stories.js.map +1 -1
  110. package/dist/stories/empty-sorters.stories.d.ts +7 -1
  111. package/dist/stories/empty-sorters.stories.js +41 -17
  112. package/dist/stories/empty-sorters.stories.js.map +1 -1
  113. package/dist/stories/explicit-fetch.stories.d.ts +5 -0
  114. package/dist/stories/explicit-fetch.stories.js +40 -17
  115. package/dist/stories/explicit-fetch.stories.js.map +1 -1
  116. package/dist/stories/fixed-column.stories.d.ts +5 -0
  117. package/dist/stories/fixed-column.stories.js +53 -30
  118. package/dist/stories/fixed-column.stories.js.map +1 -1
  119. package/dist/stories/grid-setting.stories.d.ts +20 -4
  120. package/dist/stories/grid-setting.stories.js +96 -51
  121. package/dist/stories/grid-setting.stories.js.map +1 -1
  122. package/dist/stories/grist-modes.stories.d.ts +8 -2
  123. package/dist/stories/grist-modes.stories.js +58 -35
  124. package/dist/stories/grist-modes.stories.js.map +1 -1
  125. package/dist/stories/group-header.stories.d.ts +5 -0
  126. package/dist/stories/group-header.stories.js +53 -30
  127. package/dist/stories/group-header.stories.js.map +1 -1
  128. package/dist/stories/textarea.stories.d.ts +8 -2
  129. package/dist/stories/textarea.stories.js +37 -13
  130. package/dist/stories/textarea.stories.js.map +1 -1
  131. package/dist/stories/tree-column-with-checkbox.stories.d.ts +5 -0
  132. package/dist/stories/tree-column-with-checkbox.stories.js +44 -21
  133. package/dist/stories/tree-column-with-checkbox.stories.js.map +1 -1
  134. package/dist/stories/tree-column.stories.d.ts +5 -0
  135. package/dist/stories/tree-column.stories.js +44 -21
  136. package/dist/stories/tree-column.stories.js.map +1 -1
  137. package/dist/tsconfig.tsbuildinfo +1 -1
  138. package/docs/default-value/value-generator/date-generator.md +29 -0
  139. package/docs/default-value/value-generator/hour-time-generator.md +33 -0
  140. package/docs/default-value/value-generator/minute-time-generator.md +33 -0
  141. package/docs/default-value/value-generator/month-date-generator.md +2 -0
  142. package/docs/default-value/value-generator/now-generator.md +29 -0
  143. package/docs/default-value/value-generator/time-generator.md +31 -0
  144. package/docs/default-value/value-generator/today-generator.md +29 -0
  145. package/docs/default-value/value-generator/week-date-generator.md +31 -0
  146. package/docs/default-value/value-generator/year-date-generator.md +31 -0
  147. package/package.json +15 -10
  148. package/src/data-card/data-card-field.ts +2 -2
  149. package/src/data-card/data-card-gutter-menu.ts +5 -5
  150. package/src/data-card/data-card-gutter.ts +6 -6
  151. package/src/data-card/data-card.ts +7 -9
  152. package/src/data-card/record-card.ts +9 -10
  153. package/src/data-grid/data-grid-accum-field.ts +11 -5
  154. package/src/data-grid/data-grid-body-style.ts +12 -0
  155. package/src/data-grid/data-grid-body.ts +16 -29
  156. package/src/data-grid/data-grid-field.ts +7 -2
  157. package/src/data-grid/data-grid-footer.ts +4 -2
  158. package/src/data-grid/data-grid-header.ts +8 -6
  159. package/src/data-grid/data-grid.ts +23 -1
  160. package/src/data-grid/event-handlers/data-grid-body-click-handler.ts +4 -0
  161. package/src/data-grist.ts +88 -8
  162. package/src/data-list/data-list-field.ts +5 -5
  163. package/src/data-list/data-list-gutter.ts +3 -3
  164. package/src/data-list/data-list.ts +4 -4
  165. package/src/data-list/record-partial.ts +9 -10
  166. package/src/data-manipulator.ts +5 -5
  167. package/src/data-report/data-report-field.ts +2 -1
  168. package/src/data-report/data-report-header.ts +2 -2
  169. package/src/editors/ox-grist-editor-select.ts +41 -28
  170. package/src/editors/ox-input-tree.ts +8 -8
  171. package/src/filters/filter-input-barcode.ts +1 -0
  172. package/src/filters/filter-select.ts +41 -28
  173. package/src/filters/filter-styles.ts +46 -31
  174. package/src/filters/filters-form.ts +273 -119
  175. package/src/gutters/gutter-dirty.ts +2 -2
  176. package/src/index.ts +1 -0
  177. package/src/personalizer/index.ts +1 -0
  178. package/src/personalizer/ox-grist-filter-personalizer.ts +191 -0
  179. package/src/personalizer/ox-grist-personalizer.ts +192 -0
  180. package/src/record-view/record-creator.ts +2 -2
  181. package/src/renderers/ox-grist-renderer-select.ts +41 -6
  182. package/src/renderers/ox-grist-renderer-tree.ts +8 -8
  183. package/src/sorters/sorters-control.ts +3 -3
  184. package/src/types.ts +53 -2
  185. package/stories/{accumulator.stories.ts → accumulator-format.stories.ts} +33 -12
  186. package/stories/barcode-input-filter.stories.ts +31 -6
  187. package/stories/bounded-select-filters.stories.ts +339 -0
  188. package/stories/bounded-select-record.stories.ts +342 -0
  189. package/stories/click-event.stories.ts +269 -0
  190. package/stories/creatable-only-column.stories.ts +54 -28
  191. package/stories/default-filters.stories.ts +92 -24
  192. package/stories/dynamic-editable.stories.ts +52 -28
  193. package/stories/empty-sorters.stories.ts +51 -25
  194. package/stories/explicit-fetch.stories.ts +48 -24
  195. package/stories/fixed-column.stories.ts +62 -39
  196. package/stories/grid-setting.stories.ts +120 -63
  197. package/stories/grist-modes.stories.ts +74 -46
  198. package/stories/group-header.stories.ts +61 -39
  199. package/stories/textarea.stories.ts +49 -17
  200. package/stories/tree-column-with-checkbox.stories.ts +52 -28
  201. package/stories/tree-column.stories.ts +52 -28
  202. package/themes/dark-hc.css +151 -0
  203. package/themes/dark-mc.css +151 -0
  204. package/themes/dark.css +151 -0
  205. package/themes/grist-theme.css +103 -100
  206. package/themes/light-hc.css +151 -0
  207. package/themes/light-mc.css +151 -0
  208. package/themes/light.css +151 -0
  209. package/themes/md-typescale-styles.css +100 -0
  210. package/themes/spacing.css +43 -0
  211. package/themes/state-color.css +6 -0
  212. package/dist/stories/accumulator.stories.js.map +0 -1
  213. package/themes/app-theme.css +0 -145
  214. package/themes/form-theme.css +0 -75
  215. package/themes/oops-theme.css +0 -26
  216. package/themes/report-theme.css +0 -47
@@ -5,12 +5,14 @@ import '@operato/input/ox-input-search.js'
5
5
 
6
6
  import { css, html, LitElement, PropertyValues, TemplateResult, nothing } from 'lit'
7
7
  import { customElement, property, queryAsync, state } from 'lit/decorators.js'
8
+ import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
8
9
 
10
+ import { PagePreferenceProvider } from '@operato/p13n'
9
11
  import { getDefaultValue } from '@operato/time-calculator'
10
12
 
11
- import { FilterConfigObject } from '..'
13
+ import { FilterConfigObject, FilterPreference } from '../types.js'
12
14
  import { DataGrist } from '../data-grist'
13
- import { ColumnConfig, FilterOperator, FilterValue, GristConfig } from '../types'
15
+ import { ColumnConfig, FilterOperator, FilterValue, GristConfig, PersonalGristPreference } from '../types'
14
16
  import { FilterStyles } from './filter-styles'
15
17
  import { getFilterRenderer } from './registry'
16
18
 
@@ -23,12 +25,14 @@ export type QueryFilter = {
23
25
  }
24
26
 
25
27
  @customElement('ox-filters-form')
26
- export class FiltersForm extends LitElement {
28
+ export class OxFiltersForm extends LitElement {
27
29
  static styles = [
30
+ MDTypeScaleStyles,
28
31
  FilterStyles,
29
32
  css`
30
33
  :host {
31
34
  display: flex;
35
+ padding: var(--spacing-small);
32
36
  }
33
37
 
34
38
  form {
@@ -50,8 +54,9 @@ export class FiltersForm extends LitElement {
50
54
  }
51
55
 
52
56
  @media only screen and (max-width: 460px) {
53
- label[between] {
54
- display: block;
57
+ form {
58
+ flex-direction: column;
59
+ flex-flow: column;
55
60
  }
56
61
  }
57
62
  `
@@ -62,12 +67,19 @@ export class FiltersForm extends LitElement {
62
67
  @property({ type: Boolean, attribute: 'autofocus' }) autofocus: boolean = true
63
68
  @property({ type: Boolean, attribute: 'empty', reflect: true }) empty: boolean = true
64
69
 
70
+ @state() personalConfigProvider?: PagePreferenceProvider
71
+ @state() personalConfig?: PersonalGristPreference
72
+ @state() personalFilters?: FilterPreference[]
73
+
65
74
  @state() config!: GristConfig
66
75
  @state() filterColumns: ColumnConfig[] = []
67
76
  @state() searchColumns: ColumnConfig[] = []
68
77
 
69
78
  @queryAsync('form') form!: HTMLFormElement
70
79
 
80
+ private autoUpdateTargetsOnChange: { [name: string]: string[] } = {}
81
+ private objectValue?: object
82
+
71
83
  connectedCallback(): void {
72
84
  super.connectedCallback()
73
85
 
@@ -75,6 +87,7 @@ export class FiltersForm extends LitElement {
75
87
 
76
88
  if (grist) {
77
89
  this.config = grist.compiledConfig
90
+ this.personalConfigProvider = grist.personalConfigProvider
78
91
 
79
92
  grist.addEventListener('config-change', (e: Event) => {
80
93
  this.config = (e as CustomEvent).detail
@@ -90,21 +103,42 @@ export class FiltersForm extends LitElement {
90
103
  })
91
104
 
92
105
  this.renderRoot.addEventListener('change', async (e: Event) => {
93
- this.dispatchEvent(
94
- new CustomEvent('fetch-params-change', {
95
- bubbles: true,
96
- composed: true,
97
- detail: {
98
- filters: await this.getQueryFilters(),
99
- from: 'filters-form'
106
+ const { target, detail: value } = e as CustomEvent
107
+ const name = (target as HTMLInputElement).name
108
+ const { filter } = this.filterColumns.find(filter => filter.name == name) || {}
109
+
110
+ if (this.autoUpdateTargetsOnChange[name]) {
111
+ /* 일단은 심플하게, boundTo로 연결된 필터값이 바뀌면, 폼 전체를 update하도록 함. */
112
+ ;(this.autoUpdateTargetsOnChange[name] || []).forEach(name => {
113
+ const target = this.renderRoot.querySelector(`[name='${name}']`)
114
+ if (target) {
115
+ ;(target as HTMLInputElement).value = ''
100
116
  }
101
117
  })
102
- )
118
+
119
+ await this.updateObjectValues()
120
+ this.requestUpdate()
121
+ }
122
+
123
+ const onchange = typeof filter == 'object' ? filter.onchange : null
124
+ const keepGoing = onchange ? await onchange.call(null, value ?? (target as HTMLInputElement).value, this) : true
125
+
126
+ keepGoing &&
127
+ this.dispatchEvent(
128
+ new CustomEvent('fetch-params-change', {
129
+ bubbles: true,
130
+ composed: true,
131
+ detail: {
132
+ filters: await this.getQueryFilters(),
133
+ from: 'filters-form'
134
+ }
135
+ })
136
+ )
103
137
  })
104
138
  }
105
139
  }
106
140
 
107
- buildDefaultValue(operator: string, defaultValue: any) {
141
+ buildDefaultValue(operator: FilterOperator, defaultValue: any) {
108
142
  if (defaultValue === undefined) {
109
143
  return
110
144
  }
@@ -115,28 +149,11 @@ export class FiltersForm extends LitElement {
115
149
  }
116
150
  }
117
151
 
118
- updated(changes: PropertyValues<this>) {
119
- if (changes.has('config')) {
120
- const filters = this.config.columns.filter(columnConfig => !!columnConfig.filter)
121
- this.filterColumns = filters.filter((columnConfig: ColumnConfig) => {
122
- const filter = columnConfig.filter as FilterConfigObject
123
- return filter!.operator !== 'search'
124
- })
125
- this.searchColumns = filters.filter(columnConfig => {
126
- const filter = columnConfig.filter as FilterConfigObject
127
- return filter!.operator === 'search'
128
- })
129
-
130
- const grist = this.closest('ox-grist') as DataGrist
131
-
132
- this.value = (grist?.filters || []).map(filter => {
133
- return {
134
- ...filter,
135
- value: this.buildDefaultValue(filter!.operator, filter!.value)
136
- }
137
- })
138
-
139
- this.empty = (this.searchColumns.length === 0 || this.withoutSearch) && this.filterColumns.length === 0
152
+ async updated(changes: PropertyValues<this>) {
153
+ if (changes.has('personalConfigProvider') && this.personalConfigProvider) {
154
+ this.personalConfig = await this.personalConfigProvider.load()
155
+ } else if (changes.has('config') || changes.has('personalConfig')) {
156
+ this.applyUpdatedConfiguration()
140
157
  }
141
158
  }
142
159
 
@@ -148,6 +165,7 @@ export class FiltersForm extends LitElement {
148
165
  ? html``
149
166
  : html`
150
167
  <form
168
+ class="md-typescale-body-medium-prominent"
151
169
  @submit=${(e: Event) => {
152
170
  e.stopPropagation()
153
171
  e.preventDefault()
@@ -157,93 +175,194 @@ export class FiltersForm extends LitElement {
157
175
  grist && grist.fetch()
158
176
  }}
159
177
  >
160
- ${this.searchColumns.length === 0 || this.withoutSearch
161
- ? html``
162
- : html`
163
- <ox-input-search name="search" .value=${searchValue} ?autofocus=${this.autofocus}></ox-input-search>
164
- `}
165
- ${this.filterColumns.map((column: ColumnConfig) => {
166
- const { name, header, label, filter } = column
167
-
168
- const type = (filter as FilterConfigObject).type
169
- const operator = (filter as FilterConfigObject).operator
170
- const filterLabel = (filter as FilterConfigObject).label
171
-
172
- const labelText =
173
- filterLabel !== undefined
174
- ? filterLabel
175
- : typeof label === 'object' && label.renderer
176
- ? label.renderer(column)
177
- : header.renderer(column) || name
178
-
179
- const idx = operator === 'between' ? 1 : 0
180
- const renderer = getFilterRenderer(
181
- operator === 'like' || operator === 'i_like' || operator === 'i_nlike' || operator === 'nlike'
182
- ? 'text'
183
- : type
184
- )[idx]
185
- const value =
186
- this.value?.find(filter => filter.name == name)?.value ??
187
- this.buildDefaultValue(operator!, (filter as FilterConfigObject)?.value)
188
-
189
- if (!renderer) {
190
- return html``
191
- }
192
-
193
- return type === 'boolean' || type === 'checkbox'
194
- ? renderer(column, value, this)
195
- : type !== 'select' && labelText
196
- ? html`<label filter-title ?between=${operator === 'between'}
197
- ><span>${labelText}</span> ${renderer(column, value, this)}
198
- </label> `
199
- : type !== 'select' && !labelText
200
- ? renderer(column, value, this)
201
- : operator === 'in'
202
- ? html`
203
- <ox-select
204
- name=${name}
205
- placeholder=${labelText}
206
- .value=${value}
207
- @change=${(e: CustomEvent) =>
208
- e.target?.dispatchEvent(
209
- new CustomEvent('filter-change', {
210
- detail: {
211
- name,
212
- operator,
213
- value: e.detail
214
- }
215
- })
216
- )}
217
- >
218
- <ox-popup-list multiple attr-selected="checked" with-search>
219
- ${renderer(column, value, this)}
220
- </ox-popup-list>
221
- </ox-select>
222
- `
223
- : html`
224
- <ox-select
225
- name=${name}
226
- placeholder=${labelText}
227
- .value=${value}
228
- @change=${(e: CustomEvent) =>
229
- e.target?.dispatchEvent(
230
- new CustomEvent('filter-change', {
231
- detail: {
232
- name,
233
- operator,
234
- value: e.detail
235
- }
236
- })
237
- )}
238
- >
239
- <ox-popup-list with-search> ${renderer(column, value, this)} </ox-popup-list>
240
- </ox-select>
241
- `
242
- })}
178
+ ${this.filterColumns
179
+ .filter(column => !(column.filter as FilterConfigObject).hidden)
180
+ .map((column: ColumnConfig) => {
181
+ const { name, header, label, filter } = column
182
+
183
+ const type = (filter as FilterConfigObject).type
184
+
185
+ if (type == 'search') {
186
+ return html`
187
+ <ox-input-search name="search" .value=${searchValue} ?autofocus=${this.autofocus}></ox-input-search>
188
+ `
189
+ }
190
+
191
+ const operator = (filter as FilterConfigObject).operator
192
+ const filterLabel = (filter as FilterConfigObject).label
193
+
194
+ const labelText =
195
+ filterLabel !== undefined
196
+ ? filterLabel
197
+ : typeof label === 'object' && label.renderer
198
+ ? label.renderer(column)
199
+ : header.renderer(column) || name
200
+
201
+ const idx = operator === 'between' ? 1 : 0
202
+ const renderer = getFilterRenderer(
203
+ operator === 'like' || operator === 'i_like' || operator === 'i_nlike' || operator === 'nlike'
204
+ ? 'text'
205
+ : type
206
+ )[idx]
207
+ const value =
208
+ this.value?.find(filter => filter.name == name)?.value ??
209
+ this.buildDefaultValue(operator!, (filter as FilterConfigObject)?.value)
210
+
211
+ if (!renderer) {
212
+ return html``
213
+ }
214
+
215
+ return type === 'boolean' || type === 'checkbox'
216
+ ? renderer(column, value, this)
217
+ : type !== 'select' && labelText
218
+ ? html`<label filter-title ?between=${operator === 'between'}
219
+ ><span>${labelText}</span> ${renderer(column, value, this)}
220
+ </label> `
221
+ : type !== 'select' && !labelText
222
+ ? renderer(column, value, this)
223
+ : operator === 'in'
224
+ ? html`
225
+ <ox-select
226
+ name=${name}
227
+ placeholder=${labelText}
228
+ .value=${value}
229
+ @change=${(e: CustomEvent) =>
230
+ e.target?.dispatchEvent(
231
+ new CustomEvent('filter-change', {
232
+ detail: {
233
+ name,
234
+ operator,
235
+ value: e.detail
236
+ }
237
+ })
238
+ )}
239
+ >
240
+ <ox-popup-list multiple attr-selected="checked" with-search>
241
+ ${renderer(column, value, this)}
242
+ </ox-popup-list>
243
+ </ox-select>
244
+ `
245
+ : html`
246
+ <ox-select
247
+ name=${name}
248
+ placeholder=${labelText}
249
+ .value=${value}
250
+ @change=${(e: CustomEvent) =>
251
+ e.target?.dispatchEvent(
252
+ new CustomEvent('filter-change', {
253
+ detail: {
254
+ name,
255
+ operator,
256
+ value: e.detail
257
+ }
258
+ })
259
+ )}
260
+ >
261
+ <ox-popup-list with-search> ${renderer(column, value, this)} </ox-popup-list>
262
+ </ox-select>
263
+ `
264
+ })}
243
265
  </form>
266
+ <slot name="setting"></slot>
244
267
  `
245
268
  }
246
269
 
270
+ applyUpdatedConfiguration() {
271
+ if (!this.config) {
272
+ return
273
+ }
274
+
275
+ const filters = this.config.columns.filter(columnConfig => !!columnConfig.filter)
276
+
277
+ this.filterColumns = filters.filter((columnConfig: ColumnConfig) => {
278
+ const filter = columnConfig.filter as FilterConfigObject
279
+ return filter!.operator !== 'search'
280
+ })
281
+ this.searchColumns = filters.filter(columnConfig => {
282
+ const filter = columnConfig.filter as FilterConfigObject
283
+ return filter!.operator === 'search'
284
+ })
285
+
286
+ if (this.searchColumns.length > 0 && !this.withoutSearch) {
287
+ this.filterColumns.unshift({ name: 'search', filter: { type: 'search' } } as any)
288
+ }
289
+
290
+ if (!this.personalConfig) {
291
+ this.personalFilters = this.filterColumns.map(column => {
292
+ return { name: column.name }
293
+ })
294
+ } else {
295
+ const { filters: personalFilters = [] } = this.personalConfig
296
+
297
+ if (personalFilters) {
298
+ const xfilters = this.filterColumns.map(column => {
299
+ return personalFilters.find(pFilter => pFilter.name == column.name) || { name: column.name }
300
+ })
301
+
302
+ function reorderList(a: FilterPreference[], b: FilterPreference[]): FilterPreference[] {
303
+ // 결과 배열 초기화, a 배열 길이만큼 undefined로 채움
304
+ const result = new Array(a.length)
305
+
306
+ // b 배열에 없는 아이템은 원래 위치로 채움
307
+ a.forEach((item, index) => {
308
+ if (!item.name || !b.find(bi => bi.name == item.name)) {
309
+ result[index] = item
310
+ }
311
+ })
312
+
313
+ b.forEach(item => {
314
+ const ai = a.find(ai => ai.name == item.name)
315
+ if (ai) {
316
+ result[result.findIndex(slot => slot === undefined)] = ai
317
+ }
318
+ })
319
+
320
+ return result
321
+ }
322
+
323
+ // 배열 재정렬 실행
324
+ this.personalFilters = reorderList(xfilters as any, personalFilters as any) as FilterPreference[]
325
+
326
+ this.filterColumns = this.personalFilters
327
+ .map(filter => {
328
+ const column = this.filterColumns.find(column => column.name == filter.name)
329
+ if (column?.filter) {
330
+ ;(column.filter as FilterConfigObject)!.hidden = filter.hidden
331
+ }
332
+ return column
333
+ })
334
+ .filter(Boolean) as ColumnConfig[]
335
+ }
336
+ }
337
+
338
+ const grist = this.closest('ox-grist') as DataGrist
339
+
340
+ this.value = (grist?.filters || []).map(filter => {
341
+ return {
342
+ ...filter,
343
+ value: this.buildDefaultValue(filter!.operator, filter!.value)
344
+ }
345
+ })
346
+
347
+ this.empty = (this.searchColumns.length === 0 || this.withoutSearch) && this.filterColumns.length === 0
348
+
349
+ this.autoUpdateTargetsOnChange = {}
350
+ this.filterColumns
351
+ ?.filter(({ filter }) => {
352
+ return typeof filter == 'object' && filter.boundTo && filter.boundTo.length > 0
353
+ })
354
+ .map(({ name, filter }) => {
355
+ const boundTo = (filter as FilterConfigObject).boundTo
356
+
357
+ boundTo!.forEach(to => {
358
+ const origin = this.autoUpdateTargetsOnChange[to] || []
359
+ if (name && !origin.includes(name)) {
360
+ this.autoUpdateTargetsOnChange[to] = [...origin, name]
361
+ }
362
+ })
363
+ })
364
+ }
365
+
247
366
  async getQueryFilters(): Promise<QueryFilter[]> {
248
367
  const form = await this.form
249
368
  if (!form) return []
@@ -252,6 +371,7 @@ export class FiltersForm extends LitElement {
252
371
  const search: string | undefined = formData.get('search')?.toString()
253
372
 
254
373
  var filters = this.filterColumns
374
+ .filter(column => column.name !== 'search' && !(column.filter as FilterConfigObject)!.hidden)
255
375
  .map((column: ColumnConfig) => {
256
376
  const { name, type, filter } = column
257
377
  const operator = (filter as FilterConfigObject).operator
@@ -319,4 +439,38 @@ export class FiltersForm extends LitElement {
319
439
  const input = this.renderRoot.querySelector(`form [name="${name}"]`) as HTMLInputElement
320
440
  return input?.value
321
441
  }
442
+
443
+ private async updateObjectValues() {
444
+ const form = await this.form
445
+ if (!form) return []
446
+
447
+ const formData = new FormData(form)
448
+
449
+ const object = {} as any
450
+ formData.forEach((value, key) => {
451
+ const prev = object[key]
452
+
453
+ if (key in object) {
454
+ object[key] = prev instanceof Array ? [...prev, value] : [prev, value]
455
+ } else {
456
+ object[key] = value
457
+ }
458
+ })
459
+
460
+ this.objectValue = object
461
+ }
462
+
463
+ public getFormObjectValue() {
464
+ return this.objectValue
465
+ }
466
+
467
+ reset() {
468
+ this.form
469
+ .then((form: HTMLFormElement) => {
470
+ form.reset()
471
+ })
472
+ .catch((error: any) => {
473
+ console.error('Error resetting the form:', error)
474
+ })
475
+ }
322
476
  }
@@ -9,7 +9,7 @@ import { ColumnConfig, FieldRenderer, GristEventHandler } from '../types'
9
9
  class GutterDirtyElement extends LitElement {
10
10
  static styles = css`
11
11
  :host {
12
- display: block;
12
+ display: flex;
13
13
  margin: auto;
14
14
  }
15
15
 
@@ -21,7 +21,7 @@ class GutterDirtyElement extends LitElement {
21
21
  }
22
22
 
23
23
  [add] {
24
- background-color: var(--primary-color);
24
+ background-color: var(--md-sys-color-on-primary-container);
25
25
  }
26
26
 
27
27
  [remove] {
package/src/index.ts CHANGED
@@ -12,5 +12,6 @@ export * from './gutters'
12
12
  export * from './filters'
13
13
  export * from './sorters/sorters-control'
14
14
  export * from './record-view'
15
+ export * from './personalizer'
15
16
 
16
17
  export * from './utils/list-param'
@@ -0,0 +1 @@
1
+ export * from './ox-grist-personalizer'