@mixd-id/web-scaffold 0.2.250801013 → 0.2.260801002

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 (271) hide show
  1. package/README.md +25 -1
  2. package/docs/components/AIDashboard.md +557 -0
  3. package/docs/components/Alert.md +3 -1
  4. package/docs/components/Card.md +5 -0
  5. package/docs/components/PivotList.md +207 -0
  6. package/docs/components/Textbox.md +20 -1
  7. package/docs/components/Tooltip.md +148 -0
  8. package/docs/components/WebPageBuilder.md +125 -0
  9. package/package.json +22 -89
  10. package/src/components/AIChat.vue +488 -0
  11. package/src/components/AIChatItem.vue +199 -0
  12. package/src/components/AIChatTextbox.vue +56 -0
  13. package/src/components/AIDashboard.vue +1159 -0
  14. package/src/components/Alert.vue +58 -35
  15. package/src/components/Card.vue +58 -7
  16. package/src/components/Carousel.vue +1 -1
  17. package/src/{widgets → components}/CartSetting.vue +1 -0
  18. package/src/components/Chart.vue +4 -3
  19. package/src/components/Checkbox.vue +22 -24
  20. package/src/components/CodeEditor.vue +123 -13
  21. package/src/{widgets → components}/CollapsiblePanelSetting.vue +1 -0
  22. package/src/components/ColorPicker.vue +8 -4
  23. package/src/components/ColumnSelector.vue +93 -0
  24. package/src/{widgets → components}/ComponentSetting2.vue +1 -1
  25. package/src/components/Confirm.vue +2 -0
  26. package/src/{widgets → components}/ContactForm.vue +1 -1
  27. package/src/components/CopyToClipboard.vue +0 -1
  28. package/src/components/Dashboard.vue +493 -212
  29. package/src/components/Datepicker.vue +2 -0
  30. package/src/components/Datetimepicker.vue +1 -1
  31. package/src/components/Dropdown.vue +1 -2
  32. package/src/{widgets → components}/FeatureList.vue +1 -1
  33. package/src/components/Gmaps.vue +1 -1
  34. package/src/components/HTMLEditor.vue +9 -1
  35. package/src/{widgets → components}/IconList.vue +1 -1
  36. package/src/components/Image.vue +1 -1
  37. package/src/components/ImagePreview.vue +32 -51
  38. package/src/components/LLMChat.vue +327 -0
  39. package/src/components/List.vue +267 -62
  40. package/src/components/MarkdownEdit.vue +124 -5
  41. package/src/components/MarkdownPreview.vue +4 -1
  42. package/src/{widgets → components}/MenuItem1Setting.vue +1 -0
  43. package/src/components/Metric.vue +30 -7
  44. package/src/components/MultiDropdown.vue +3 -1
  45. package/src/{widgets → components}/MultiValueSetting.vue +2 -2
  46. package/src/{widgets → components}/MultiValueSetting2.vue +2 -2
  47. package/src/components/OTPField.vue +3 -1
  48. package/src/components/PivotList.vue +1239 -0
  49. package/src/{widgets → components}/PresetBar.vue +32 -9
  50. package/src/{widgets → components}/PresetBarPivot.vue +147 -15
  51. package/src/{widgets → components}/PresetBarPivotColumnEdit.vue +8 -19
  52. package/src/components/PresetBarPivotValueEdit.vue +172 -0
  53. package/src/{widgets → components}/PresetSelector.vue +8 -4
  54. package/src/components/PresetSelectorFilterItem.vue +2 -1
  55. package/src/components/Radio.vue +1 -1
  56. package/src/{widgets → components}/Share.vue +1 -1
  57. package/src/components/SvgEditor.vue +1 -1
  58. package/src/components/Switch.vue +1 -1
  59. package/src/components/TextWithTag.vue +5 -5
  60. package/src/components/Textbox.vue +63 -5
  61. package/src/components/Toast.vue +2 -1
  62. package/src/components/Tooltip.vue +579 -96
  63. package/src/components/TreeView.vue +1 -1
  64. package/src/components/VirtualGrid.vue +2 -2
  65. package/src/components/VirtualTable.vue +170 -78
  66. package/src/{widgets → components}/WebDatasourceSelector.vue +2 -0
  67. package/src/{widgets → components}/WebLayoutSelector.vue +1 -1
  68. package/src/{widgets → components}/WebPageBuilder.vue +153 -352
  69. package/src/directives/tooltip.js +131 -0
  70. package/src/index.js +11 -287
  71. package/src/instructions/dashboard.js +203 -0
  72. package/src/themes/default/index.js +16 -0
  73. package/src/utils/ai-chat-answer.mjs +240 -0
  74. package/src/utils/ai-chat.js +167 -0
  75. package/src/utils/ai-dashboard-chat.mjs +371 -0
  76. package/src/utils/ai-dashboard.js +446 -0
  77. package/src/utils/ai-list-chat.mjs +407 -0
  78. package/src/utils/dashboard.js +13 -0
  79. package/src/utils/helpers.mjs +52 -5
  80. package/src/utils/list.mjs +150 -92
  81. package/src/utils/pivot-formula.mjs +358 -0
  82. package/src/utils/pivot-list.mjs +557 -0
  83. package/src/utils/preset-selector.mjs +183 -27
  84. package/src/utils/tooltip.mjs +243 -0
  85. package/src/utils/wss.js +26 -8
  86. package/src/utils/wss.mjs +29 -2
  87. package/docs/components/ArrayList.md +0 -39
  88. package/docs/components/Box.md +0 -15
  89. package/docs/components/ChartBar.md +0 -34
  90. package/docs/components/DayTimeRange.md +0 -20
  91. package/docs/components/ErrorText.md +0 -15
  92. package/docs/components/FBLogin.md +0 -21
  93. package/docs/components/GHeatMaps.md +0 -16
  94. package/docs/components/GSignIn.md +0 -21
  95. package/docs/components/GmapsDirection.md +0 -20
  96. package/docs/components/IconMenu.md +0 -20
  97. package/src/components/ArrayList.vue +0 -49
  98. package/src/components/Box.vue +0 -34
  99. package/src/components/ChartBar.vue +0 -192
  100. package/src/components/DayTimeRange.vue +0 -126
  101. package/src/components/ErrorText.vue +0 -57
  102. package/src/components/FBLogin.vue +0 -194
  103. package/src/components/GHeatMaps.vue +0 -317
  104. package/src/components/GSignIn.vue +0 -166
  105. package/src/components/GmapsDirection.vue +0 -191
  106. package/src/components/IconMenu.vue +0 -95
  107. package/src/components/IconPlus.vue +0 -89
  108. package/src/components/Image360.vue +0 -140
  109. package/src/components/ImageFullScreen.vue +0 -101
  110. package/src/components/ImportModal.vue +0 -260
  111. package/src/components/ListContextMenu.vue +0 -88
  112. package/src/components/LogViewerItem.vue +0 -116
  113. package/src/components/MultilineText.vue +0 -174
  114. package/src/components/PageBuilder.vue +0 -449
  115. package/src/components/SplitPane.vue +0 -129
  116. package/src/components/TabView.vue +0 -105
  117. package/src/components/TableView.vue +0 -200
  118. package/src/components/TableViewHead.vue +0 -159
  119. package/src/components/Test.vue +0 -23
  120. package/src/components/TextEditor.vue +0 -85
  121. package/src/components/TreeViewItem1.vue +0 -282
  122. package/src/components/VirtualScroll.vue +0 -282
  123. package/src/configs/dashboard/bar.js +0 -10
  124. package/src/configs/dashboard/collection-1.js +0 -5
  125. package/src/configs/dashboard/data-table.js +0 -9
  126. package/src/configs/dashboard/doughnut.js +0 -7
  127. package/src/configs/dashboard/gheatmaps.js +0 -9
  128. package/src/configs/dashboard/grid-2.js +0 -34
  129. package/src/configs/dashboard/grid-3.js +0 -34
  130. package/src/configs/dashboard/grid-4.js +0 -34
  131. package/src/configs/dashboard/grid.js +0 -15
  132. package/src/configs/dashboard/metric.js +0 -10
  133. package/src/configs/dashboard/pie.js +0 -7
  134. package/src/configs/dashboard/polar-area.js +0 -7
  135. package/src/configs/dashboard/virtual-table.js +0 -9
  136. package/src/configs/icons-fa.json +0 -12
  137. package/src/middleware/http/trim-string.js +0 -20
  138. package/src/mixin/ready-state.js +0 -37
  139. package/src/stores/components.js +0 -36
  140. package/src/stores/datasource.js +0 -11
  141. package/src/utils/event-bus.js +0 -8
  142. package/src/utils/helpers.cjs +0 -635
  143. package/src/utils/importer.js +0 -209
  144. package/src/utils/listview.js +0 -1287
  145. package/src/utils/preset-selector.cjs +0 -1455
  146. package/src/utils/preset-selector.js +0 -1452
  147. package/src/utils/queue.js +0 -63
  148. package/src/utils/web.js +0 -50
  149. package/src/utils/web.mjs +0 -120
  150. package/src/widgets/BackgroundSetting.vue +0 -132
  151. package/src/widgets/BorderSetting.vue +0 -201
  152. package/src/widgets/BotEditor/BotEditorActions.vue +0 -163
  153. package/src/widgets/BotEditor/BotEditorSettings.vue +0 -21
  154. package/src/widgets/BotEditor.vue +0 -248
  155. package/src/widgets/BoxSetting.vue +0 -53
  156. package/src/widgets/CheckoutSetting.vue +0 -46
  157. package/src/widgets/ColumnSelector.vue +0 -104
  158. package/src/widgets/ComponentPropertyLabel.vue +0 -35
  159. package/src/widgets/ComponentPropertySelector.vue +0 -115
  160. package/src/widgets/ComponentSetting3.vue +0 -231
  161. package/src/widgets/EffectSetting.vue +0 -94
  162. package/src/widgets/FiltersSetting.vue +0 -88
  163. package/src/widgets/Header.vue +0 -15
  164. package/src/widgets/Header0.vue +0 -41
  165. package/src/widgets/Header0Setting.vue +0 -75
  166. package/src/widgets/HeaderSetting.vue +0 -102
  167. package/src/widgets/LayoutSetting.vue +0 -251
  168. package/src/widgets/LogViewer.vue +0 -85
  169. package/src/widgets/MarginPaddingSetting.vue +0 -58
  170. package/src/widgets/MarginSetting.vue +0 -142
  171. package/src/widgets/MenuEditor.vue +0 -251
  172. package/src/widgets/PaddingSetting.vue +0 -142
  173. package/src/widgets/SizingSetting.vue +0 -306
  174. package/src/widgets/SpacingSetting.vue +0 -77
  175. package/src/widgets/StyleSetting.vue +0 -543
  176. package/src/widgets/StyleSetting2.vue +0 -90
  177. package/src/widgets/TransitionSetting.vue +0 -64
  178. package/src/widgets/TypographySetting.vue +0 -334
  179. package/src/widgets/UserActionBuilder/UserActionCondition.vue +0 -99
  180. package/src/widgets/UserActionBuilder/UserActionConsole.vue +0 -97
  181. package/src/widgets/UserActionBuilder/UserActionItem.vue +0 -273
  182. package/src/widgets/UserActionBuilder/UserActionOutput.vue +0 -150
  183. package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +0 -27
  184. package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +0 -28
  185. package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +0 -112
  186. package/src/widgets/UserActionBuilder/UserActionProps.vue +0 -213
  187. package/src/widgets/UserActionBuilder.vue +0 -314
  188. package/src/widgets/WebPageBuilder4/BackgroundColorSetting.vue +0 -54
  189. package/src/widgets/WebPageBuilder4/BackgroundPositionSetting.vue +0 -75
  190. package/src/widgets/WebPageBuilder4/BackgroundRepeatSetting.vue +0 -72
  191. package/src/widgets/WebPageBuilder4/BackgroundSizeSetting.vue +0 -72
  192. package/src/widgets/WebPageBuilder4/BlurSetting.vue +0 -72
  193. package/src/widgets/WebPageBuilder4/BorderColorSetting.vue +0 -54
  194. package/src/widgets/WebPageBuilder4/BorderRadiusSetting.vue +0 -75
  195. package/src/widgets/WebPageBuilder4/BorderSizeSetting.vue +0 -69
  196. package/src/widgets/WebPageBuilder4/BorderStyleSetting.vue +0 -72
  197. package/src/widgets/WebPageBuilder4/BoxShadowSetting.vue +0 -73
  198. package/src/widgets/WebPageBuilder4/ButtonSetting.vue +0 -65
  199. package/src/widgets/WebPageBuilder4/CarouselSetting.vue +0 -178
  200. package/src/widgets/WebPageBuilder4/DisplaySetting.vue +0 -76
  201. package/src/widgets/WebPageBuilder4/FlexAlignSetting.vue +0 -71
  202. package/src/widgets/WebPageBuilder4/FlexDirectionSetting.vue +0 -70
  203. package/src/widgets/WebPageBuilder4/FlexJustifySetting.vue +0 -74
  204. package/src/widgets/WebPageBuilder4/FlexPropertySetting.vue +0 -71
  205. package/src/widgets/WebPageBuilder4/FlexSetting.vue +0 -96
  206. package/src/widgets/WebPageBuilder4/FlexWrapSetting.vue +0 -69
  207. package/src/widgets/WebPageBuilder4/GapSetting.vue +0 -75
  208. package/src/widgets/WebPageBuilder4/GrayscaleSetting.vue +0 -68
  209. package/src/widgets/WebPageBuilder4/GridSetting.vue +0 -216
  210. package/src/widgets/WebPageBuilder4/HeightSetting.vue +0 -98
  211. package/src/widgets/WebPageBuilder4/ImageSetting.vue +0 -102
  212. package/src/widgets/WebPageBuilder4/MarginSetting.vue +0 -73
  213. package/src/widgets/WebPageBuilder4/MaxHeightSetting.vue +0 -82
  214. package/src/widgets/WebPageBuilder4/MaxWidthSetting.vue +0 -90
  215. package/src/widgets/WebPageBuilder4/MinHeightSetting.vue +0 -81
  216. package/src/widgets/WebPageBuilder4/MinWidthSetting.vue +0 -101
  217. package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +0 -163
  218. package/src/widgets/WebPageBuilder4/OpacitySetting.vue +0 -77
  219. package/src/widgets/WebPageBuilder4/OverflowSetting.vue +0 -81
  220. package/src/widgets/WebPageBuilder4/PaddingSetting.vue +0 -57
  221. package/src/widgets/WebPageBuilder4/PositionSetting.vue +0 -191
  222. package/src/widgets/WebPageBuilder4/PropertySetting.vue +0 -176
  223. package/src/widgets/WebPageBuilder4/TextSetting.vue +0 -72
  224. package/src/widgets/WebPageBuilder4/TreeView.vue +0 -46
  225. package/src/widgets/WebPageBuilder4/TreeViewItem.vue +0 -303
  226. package/src/widgets/WebPageBuilder4/WebPageComponentSelector.vue +0 -141
  227. package/src/widgets/WebPageBuilder4/WebPagePropertySelector.vue +0 -168
  228. package/src/widgets/WebPageBuilder4/WidthSetting.vue +0 -105
  229. package/src/widgets/WebPageBuilder4/ZIndexSetting.vue +0 -72
  230. package/src/widgets/WebPagePreview.vue +0 -295
  231. package/src/widgets/WebPageSelector.vue +0 -173
  232. package/src/widgets/WebTemplateCreator.vue +0 -156
  233. /package/src/{widgets → components}/AhrefSetting.vue +0 -0
  234. /package/src/{widgets → components}/ArticleSetting.vue +0 -0
  235. /package/src/{widgets → components}/BackgroundColorSetting.vue +0 -0
  236. /package/src/{widgets → components}/BlockSetting.vue +0 -0
  237. /package/src/{widgets → components}/BorderColorSetting.vue +0 -0
  238. /package/src/{widgets → components}/ButtonSetting.vue +0 -0
  239. /package/src/{widgets → components}/CarouselSetting.vue +0 -0
  240. /package/src/{widgets → components}/ComponentSetting.vue +0 -0
  241. /package/src/{widgets → components}/ContactFormSetting.vue +0 -0
  242. /package/src/{widgets → components}/CountdownSetting.vue +0 -0
  243. /package/src/{widgets → components}/DataListSetting.vue +0 -0
  244. /package/src/{widgets → components}/EmbeddedVideo.vue +0 -0
  245. /package/src/{widgets → components}/EmbeddedVideoSetting.vue +0 -0
  246. /package/src/{widgets → components}/FAQ.vue +0 -0
  247. /package/src/{widgets → components}/FAQSetting.vue +0 -0
  248. /package/src/{widgets → components}/FeatureListSetting.vue +0 -0
  249. /package/src/{widgets → components}/FlexSetting.vue +0 -0
  250. /package/src/{widgets → components}/GridSetting.vue +0 -0
  251. /package/src/{widgets → components}/Header2.vue +0 -0
  252. /package/src/{widgets → components}/Header2Setting.vue +0 -0
  253. /package/src/{widgets → components}/IconListSetting.vue +0 -0
  254. /package/src/{widgets → components}/ImageSetting.vue +0 -0
  255. /package/src/{widgets → components}/LinkSetting.vue +0 -0
  256. /package/src/{widgets → components}/LinkSettingModal.vue +0 -0
  257. /package/src/{widgets → components}/ModalSetting.vue +0 -0
  258. /package/src/{widgets → components}/OGSettingModal.vue +0 -0
  259. /package/src/{widgets → components}/ParagraphSetting.vue +0 -0
  260. /package/src/{widgets → components}/PositionSetting.vue +0 -0
  261. /package/src/{widgets → components}/Review.vue +0 -0
  262. /package/src/{widgets → components}/ReviewSetting.vue +0 -0
  263. /package/src/{widgets → components}/SearchModalSetting.vue +0 -0
  264. /package/src/{widgets → components}/ShareSetting.vue +0 -0
  265. /package/src/{widgets → components}/SvgSetting.vue +0 -0
  266. /package/src/{widgets → components}/TableSetting.vue +0 -0
  267. /package/src/{widgets → components}/TestimonialSetting.vue +0 -0
  268. /package/src/{widgets → components}/TextBlockSetting.vue +0 -0
  269. /package/src/{widgets → components}/ThumbnailSetting.vue +0 -0
  270. /package/src/{widgets → components}/WebComponentSelector.vue +0 -0
  271. /package/src/{widgets → components}/YoutubeVideoSetting.vue +0 -0
@@ -0,0 +1,207 @@
1
+ # PivotList
2
+
3
+ A pivot-first data list. Same structure and preset system as `List`, but the
4
+ preset is **always** in pivot mode and the result renders in a **single
5
+ `VirtualTable`**. Built for aggregating a Sequelize model — including columns
6
+ that live across **relationships** (`customer.name`, `order.customer.city`) —
7
+ with client-side **virtual columns** edited in a spreadsheet-style formula bar.
8
+
9
+ ## TL;DR — how to use
10
+
11
+ **1. Client** (any CMS front end):
12
+
13
+ ```vue
14
+ <PivotList title="Sales"
15
+ controller="order"
16
+ preset-key="order-pivot"
17
+ subscribe-key="order"
18
+ :default-config="defaultConfig"
19
+ @pivot-item-click="drillDown" />
20
+ ```
21
+
22
+ ```js
23
+ import PivotList from '@mixd-id/web-scaffold/components/PivotList.vue'
24
+
25
+ const defaultConfig = () => ({
26
+ name: 'Orders',
27
+ columns: [
28
+ { key: 'status', label: 'Status', filterable: true, search: true },
29
+ { key: 'amount', label: 'Amount', type: 'currency', filterable: true },
30
+ { key: 'orderedAt', label: 'Ordered', type: 'date', filterable: true },
31
+ // relationship column — dotted key resolves through model.associations
32
+ { key: 'customer.city', label: 'City', filterable: true },
33
+ ],
34
+ presets: [{
35
+ name: 'By city / month',
36
+ pivot: {
37
+ rows: [
38
+ { key: 'customer.city' },
39
+ { key: 'orderedAt', aggregate: 'month' },
40
+ ],
41
+ values: [
42
+ { key: 'amount', aggregate: 'sum' },
43
+ { key: 'id', aggregate: 'count' },
44
+ ],
45
+ sorts: [ { key: '_amount-sum', type: 'desc' } ],
46
+ useTotal: true,
47
+ },
48
+ }],
49
+ })
50
+
51
+ const drillDown = ({ item, column, filters }) => {
52
+ // filters are ready-made List filters equivalent to the clicked cell
53
+ // — e.g. navigate to the orders List with them applied
54
+ }
55
+ ```
56
+
57
+ **2. Server** — apply the mixin once on the model base class
58
+ (`server/src/classes/model.js`):
59
+
60
+ ```js
61
+ import { PivotListMixin } from '@mixd-id/web-scaffold/pivot-list'
62
+
63
+ class CustomModel extends PivotListMixin(Model) { ... }
64
+ ```
65
+
66
+ then add the socket handler the component calls (`${controller}.pivot-load`):
67
+
68
+ ```js
69
+ // server/src/controllers/sockets/order.js
70
+ async 'pivot-load'(preset){
71
+ return Order.findAllFromPivotPreset(preset, {
72
+ config: { columns: [ ...same column defs... ] },
73
+ Sequelize
74
+ })
75
+ }
76
+ ```
77
+
78
+ (Or skip the mixin and call the standalone helper:
79
+ `findAllFromPivotPreset(Order, preset, { config })`.)
80
+
81
+ **3. Presets** persist exactly like `List`: pass `preset-key` and the config
82
+ (presets, pivot setup, virtual columns, sidebar state) auto-saves to
83
+ `${controller}.preset` (falling back to `user.preset`) a moment after any
84
+ change, and reloads on mount. No extra wiring if your controller already
85
+ supports `List` presets.
86
+
87
+ That's it — the sidebar (`Alt+\``) gives users groups, aggregates, crosstab
88
+ spreads, ordering, top-N limits, filters and sharing; the formula bar adds
89
+ calculated columns without touching the server.
90
+
91
+ ## How it differs from List
92
+
93
+ | | List | PivotList |
94
+ |---|---|---|
95
+ | Pivot | optional per preset | always on (`pivot.enabled` is forced) |
96
+ | Result view | table or grid, pivot drill-down opens a second table | one `VirtualTable`, always |
97
+ | Pagination | infinite scroll (`afterItem`) | none — one grouped result set, cap with `pivot.limit` |
98
+ | Drill-down | opens the built-in ext panel | emits `pivot-item-click` with ready-made filters; the host decides |
99
+ | Relationships | filter-only (`filtersToSequelizeInclude`) | groups, aggregates, spreads, filters and sorts on dotted keys |
100
+ | Virtual columns | — | client-side formula columns (formula bar) |
101
+ | Server endpoint | `${controller}.load` | `${controller}.pivot-load` |
102
+
103
+ ## Data-type behavior
104
+
105
+ Column types come from your config or, when omitted, from the model
106
+ (`columnTypes` in the response — dotted relationship keys included). The type
107
+ changes what the pivot does with a column:
108
+
109
+ | Type | As a group (row) | As a value (aggregate) | Default aggregate |
110
+ |---|---|---|---|
111
+ | `number` / `currency` | groups raw values | `sum`, `avg`, `min`, `max`, `stddev`, `count`, `countDistinct`, `first`, `last` | `sum` |
112
+ | `date` | buckets: `hour`, `date`, `week`, `month`, `quarter`, `year` | `min` (earliest), `max` (latest), `count`, `countDistinct` | `max` |
113
+ | `boolean` | groups true/false | `countTrue`, `countFalse`, `count` | `countTrue` |
114
+ | `string` / `enum` | groups raw values | `count`, `countDistinct`, `groupConcat` (distinct values in one cell), `min`, `max` | `count` |
115
+
116
+ An aggregate that doesn't fit the column's type falls back to the type's
117
+ default (e.g. `sum` on a string becomes `count`). Value-returning aggregates
118
+ (`min`/`max`/`first`/`last`) keep the source column's type and format in the
119
+ result — a date's `max` renders as a date, an enum's `first` resolves its
120
+ enum label.
121
+
122
+ Drill-down filters are also type-aware: boolean cells compare as booleans and
123
+ date buckets become `fn: 'format_month'`-style filters, on both own and
124
+ relationship columns.
125
+
126
+ ## Relationships (Sequelize)
127
+
128
+ Any dotted key resolves through `model.associations`, joining as needed:
129
+
130
+ ```js
131
+ rows: [ { key: 'customer.city' } ] // GROUP BY `customer`.`city`
132
+ values: [ { key: 'customer.name', aggregate: 'countDistinct' } ]
133
+ filters:[ { key: 'customer.city', value: [ { operator: 'contains', value: 'Jak' } ] } ]
134
+ ```
135
+
136
+ Nested paths work too (`order.customer.company.name` →
137
+ `` `order->customer->company`.`name` ``). Joins are `LEFT OUTER` with
138
+ `attributes: []` — they exist only for grouping/aggregating.
139
+
140
+ Caveats:
141
+ - **hasMany joins multiply rows.** Aggregating a parent measure while joining
142
+ a hasMany relation inflates `sum`/`count` — prefer pivoting from the child
143
+ model, or use `countDistinct`.
144
+ - `first`/`last` are only supported on own columns (they resolve via the root
145
+ table's id); on dotted keys they fall back to `count`.
146
+
147
+ ## Virtual columns (formula bar)
148
+
149
+ The `fx` bar above the table adds columns **calculated client-side** per pivot
150
+ row — including the total row — like a spreadsheet formula:
151
+
152
+ ```
153
+ =([_amount-sum] - [_cost-sum]) / [_amount-sum] * 100
154
+ =IF([_id-count] > 100, "High", "Low")
155
+ =ROUND([_amount-sum] / [_qty-sum], 2)
156
+ ```
157
+
158
+ - Reference pivot result columns as `[key]` — focus the input and click a
159
+ chip to insert one. Aggregate aliases follow the `List` contract:
160
+ `_<key>-<aggregate>`, spreads `_<key>-<aggregate>-<group>`, plus
161
+ `_rowTotal`.
162
+ - Operators: `+ - * / % ^`, `&` (concat), comparisons `= <> > >= < <=`.
163
+ - Functions: `IF`, `AND`, `OR`, `NOT`, `ABS`, `ROUND`, `FLOOR`, `CEIL`,
164
+ `MIN`, `MAX`, `SUM`, `AVG`, `COUNT`, `CONCAT`, `LEN`, `UPPER`, `LOWER`.
165
+ - Blank cells count as `0`; per-cell failures show Excel-style codes
166
+ (`#DIV/0!`, `#VALUE!`, `#NAME?`) without breaking the column.
167
+ - Stored on the preset (`pivot.virtualColumns`), so they persist and share
168
+ with the preset; recomputed locally after every load — the server never
169
+ sees them.
170
+
171
+ ## Props
172
+
173
+ | Property | Type | Default | Description |
174
+ |----------|------|---------|-------------|
175
+ | config | Object | `undefined` | Preset config (columns/presets); omit to use `defaultConfig` + `presetKey` |
176
+ | defaultConfig | Function | `undefined` | Returns the default config when no saved preset exists |
177
+ | presetKey | String | `undefined` | Persistence key — enables preset load/save |
178
+ | controller | String | `undefined` | Socket controller prefix (`<controller>.pivot-load`, `<controller>.preset`) |
179
+ | src | String | `` `${controller}.pivot-load` `` | Override the load endpoint |
180
+ | subscribeKey | String | `undefined` | Realtime channel; signals trigger a debounced pivot reload |
181
+ | title | String | `'Untitled'` | Small title above the preset name |
182
+ | updateInterval | Number | `1200` | Debounce (ms) for realtime reloads |
183
+ | presetMode | String | `'sidebar'` | `'sidebar'`, `'popup'` or `'external'` (emits `open-preset`) |
184
+ | searchable | Boolean/String | `true` | Show the search box (columns with `search: true` are matched) |
185
+ | toolbar | Boolean/String | `true` | Show the right toolbar + formula bar |
186
+ | headerClass / containerClass / itemClass | String | — | Styling pass-throughs (same as `List`) |
187
+ | itemsFn | Function | `undefined` | Transform the loaded rows before render |
188
+
189
+ ## Events
190
+
191
+ | Event | Payload | Description |
192
+ |-------|---------|-------------|
193
+ | pivot-item-click | `{ item, column, filters }` | Aggregate cell clicked — `filters` reproduce the cell's row slice |
194
+ | item-click | `(item, column)` | Group-column or virtual-column cell clicked |
195
+ | after-load | — | Pivot data loaded |
196
+ | signal | `(event, items)` | Realtime signal received (reload already scheduled) |
197
+ | open-preset | — | `presetMode="external"` asked to open the preset editor |
198
+
199
+ ## Slots
200
+
201
+ `head`, `left`, `left-toolbar`, `toolbar`, `right-toolbar`, `mobile-toolbar`,
202
+ and per-column `col-<key>` header/content slots — same contract as `List`.
203
+
204
+ ## Keyboard
205
+
206
+ - `Alt+\`` — toggle the preset sidebar
207
+ - `Alt+1..9` — switch preset
@@ -6,7 +6,7 @@ A versatile text input component with support for various types, formatting, and
6
6
  |----------|------|---------|-------------|
7
7
  | modelValue | Any | `undefined` | The input value. Supports `v-model` |
8
8
  | value | Any | `undefined` | Alternative value prop |
9
- | type | String | `undefined` | Input type: `'text'`, `'tel'`, `'password'`, `'number'` |
9
+ | type | String | `undefined` | Input type: `'text'`, `'tel'`, `'password'`, `'number'`. `tel` accepts digits and one decimal separator — see below |
10
10
  | placeholder | String | `undefined` | Placeholder text |
11
11
  | readonly | Any | `undefined` | Makes the input read-only |
12
12
  | disabled | Any | `undefined` | Disables the input |
@@ -63,6 +63,25 @@ A versatile text input component with support for various types, formatting, and
63
63
  <Textbox v-model="phone" type="tel" placeholder="+62..." />
64
64
  ```
65
65
 
66
+ ### `type="tel"` and decimals
67
+
68
+ `tel` takes digits and **one** decimal separator. Either key produces it — a dot and a comma
69
+ mean the same thing here, because which one a keyboard offers depends on the layout, and
70
+ being told the key you have is the wrong one is not a useful thing for a number field to say.
71
+
72
+ What is shown follows the locale (`Intl.NumberFormat` — a dot in `en`, a comma in `id`, `de`,
73
+ `fr`), and **what is emitted is always written with a dot**, so `Number()` and `v-model.number`
74
+ read it the same wherever it was typed:
75
+
76
+ | typed | shown (en) | shown (id) | emitted |
77
+ |---|---|---|---|
78
+ | `0.7` | `0.7` | `0,7` | `0.7` |
79
+ | `0,7` | `0.7` | `0,7` | `0.7` |
80
+ | `1,2,3` | `1.23` | `1,23` | `1.23` |
81
+
82
+ Only the first separator is one; anything after it belongs to the fraction. `maxlength` counts
83
+ the separator, so a field expecting `0.75` needs at least 4.
84
+
66
85
  ### With clear button
67
86
  ```vue
68
87
  <Textbox v-model="search" clearable @clear="search = ''" />
@@ -0,0 +1,148 @@
1
+ # Tooltip
2
+ Floating label shown on hover, keyboard focus, or long-press. A single tooltip layer is mounted lazily on first use and reused for every tooltip in the app, so only one tooltip element ever exists in the DOM.
3
+
4
+ Three ways to use it:
5
+
6
+ - `v-tooltip` directive — the common case, works on any element.
7
+ - `<Tooltip>` component — wraps a trigger, useful when you want props instead of a binding object.
8
+ - `showTooltip()` / `hideTooltip()` — programmatic, for custom triggers.
9
+
10
+ ## Props
11
+ | Property | Type | Default | Description |
12
+ |----------|------|---------|-------------|
13
+ | text | String | `undefined` | Tooltip body. Supports `\n` for multiple lines |
14
+ | title | String | `undefined` | Optional bold first line, for a label + description tooltip |
15
+ | shortcut | String | `undefined` | Keyboard hint rendered as a chip, e.g. `⌘S` |
16
+ | placement | String | `'top'` | `'top'`, `'bottom'`, `'left'`, `'right'`. Flips automatically when it doesn't fit |
17
+ | variant | String | `'base'` | `'base'`, `'primary'`, `'secondary'`, `'accent'`, `'danger'`, `'success'` |
18
+ | delay | Number | `null` | Hover intent in ms before showing (default `400`). `0` shows immediately |
19
+ | offset | Number | `8` | Gap in px between the anchor and the tooltip |
20
+ | maxWidth | Number/String | `260` | Max width of the tooltip surface |
21
+ | arrow | Boolean | `true` | Show the arrow pointing at the anchor |
22
+ | disabled | Boolean | `false` | Never show this tooltip |
23
+ | open | Boolean | `null` | Controlled mode — when set, hover/focus is ignored |
24
+ | tag | String | `'span'` | Element used for the trigger wrapper |
25
+ | singleton | Boolean | `false` | Renders the shared layer instead of a trigger. Set by the controller — you never need this |
26
+
27
+ ## Events
28
+ | Event | Payload | Description |
29
+ |-------|---------|-------------|
30
+ | show | - | Emitted when the tooltip actually becomes visible for this trigger (after the hover-intent delay) |
31
+ | hide | - | Emitted when it stops being visible — including self-hides such as `Escape` or scrolling the anchor away |
32
+ | update:open | Boolean | Emitted alongside `show`/`hide`, enables `v-model:open` |
33
+
34
+ Events come from the wrapper component only; the `v-tooltip` directive doesn't emit.
35
+
36
+ ## Methods
37
+ | Method | Parameters | Description |
38
+ |--------|------------|-------------|
39
+ | show | - | Shows the tooltip |
40
+ | hide | `immediate` | Hides it, skipping the hide delay when `immediate` is true |
41
+
42
+ ## Slots
43
+ | Slot | Description |
44
+ |------|-------------|
45
+ | default | The trigger element the tooltip is anchored to |
46
+
47
+ ## Directive
48
+
49
+ ```vue
50
+ <button v-tooltip="'Save changes'">Save</button>
51
+ ```
52
+
53
+ Placement via argument or modifier, variant via modifier, `.now` to skip the delay:
54
+
55
+ ```vue
56
+ <button v-tooltip:bottom="'Filters'">…</button>
57
+ <button v-tooltip.right.primary="'Filters'">…</button>
58
+ <button v-tooltip.now="'Copied'">…</button>
59
+ ```
60
+
61
+ Object form, for anything richer:
62
+
63
+ ```vue
64
+ <button v-tooltip="{ title: 'Save', text: 'Writes to the server', shortcut: '⌘S', placement: 'bottom' }">
65
+ Save
66
+ </button>
67
+ ```
68
+
69
+ The binding is read at show time, so a value that starts empty and fills in later works — as does `v-tooltip` on a **disabled** button, which the directive hit-tests separately because disabled controls never fire mouse events.
70
+
71
+ ## Samples
72
+
73
+ ### Basic
74
+ ```vue
75
+ <Tooltip text="Delete this row">
76
+ <button>Delete</button>
77
+ </Tooltip>
78
+ ```
79
+
80
+ ### Title, description and shortcut
81
+ ```vue
82
+ <Tooltip title="Publish" text="Makes the page visible to everyone" shortcut="⌘↵" placement="bottom">
83
+ <Button text="Publish" />
84
+ </Tooltip>
85
+ ```
86
+
87
+ ### Variants
88
+ ```vue
89
+ <Tooltip text="Looks good" variant="success"><span>✓</span></Tooltip>
90
+ <Tooltip text="Quota exceeded" variant="danger"><span>!</span></Tooltip>
91
+ ```
92
+
93
+ ### Controlled
94
+ ```vue
95
+ <Tooltip text="Copied to clipboard" :open="justCopied" placement="right">
96
+ <button @click="copy">Copy</button>
97
+ </Tooltip>
98
+ ```
99
+
100
+ ### Reacting to show/hide
101
+ ```vue
102
+ <Tooltip text="Writes to the server" @show="track('tooltip-seen')" @hide="onHidden">
103
+ <button>Save</button>
104
+ </Tooltip>
105
+ ```
106
+
107
+ `v-model:open` keeps a controlled tooltip in sync when it closes itself (Escape, scrolling away):
108
+
109
+ ```vue
110
+ <Tooltip text="Copied to clipboard" v-model:open="justCopied">
111
+ <button @click="copy">Copy</button>
112
+ </Tooltip>
113
+ ```
114
+
115
+ ### Programmatic
116
+ ```js
117
+ import { showTooltip, hideTooltip } from '@mixd-id/web-scaffold/tooltip'
118
+
119
+ showTooltip(el, { text: 'Drop files here', placement: 'bottom', delay: 0 })
120
+ hideTooltip(el)
121
+ ```
122
+
123
+ ## Behaviour
124
+ - Shows after `400ms` of hover intent; while a tooltip is already open (or one closed within `320ms`) the next one appears instantly, so scanning a toolbar doesn't stutter.
125
+ - Hides on mouse leave, click, blur, `Escape`, and when the anchor scrolls out of view. It repositions itself while the page scrolls or resizes.
126
+ - Keyboard focus shows it only for `:focus-visible`, so clicking a button doesn't leave a tooltip behind.
127
+ - Touch devices: long-press (`450ms`) shows it, and it auto-hides after `2.5s`.
128
+ - Text is rendered as text, never HTML.
129
+
130
+ ## Styling
131
+ The layer is global CSS (class `mx-tooltip`) and paints itself entirely from the app's theme tokens — no colours of its own — so it follows whatever theme is active, light or dark:
132
+
133
+ | Part | Token |
134
+ |------|-------|
135
+ | surface, arrow | `bg-base-500`, `border-border-100` |
136
+ | text | `text-text` |
137
+ | description under a title | `text-text-400` |
138
+ | shortcut chip | `bg-base-300`, `text-text-400` |
139
+ | `primary` / `secondary` / `accent` | that tone's `-500` surface, `-400` border, `-50` / `-100` text |
140
+
141
+ Only layout knobs are exposed as custom properties:
142
+
143
+ ```css
144
+ .mx-tooltip{
145
+ --mx-tooltip-max-width: 320px;
146
+ --mx-tooltip-z: 12000;
147
+ }
148
+ ```
@@ -0,0 +1,125 @@
1
+ # WebPageBuilder
2
+ A full-screen visual page builder widget. It renders a three-pane editor — settings panel (page info / layout / components / chat), a live preview iframe, and a per-component setting panel — and talks to a socket controller for loading, saving and previewing pages.
3
+
4
+ Registered globally by the scaffold plugin as an async component, so it can be used directly without importing.
5
+
6
+ ## Requirements
7
+ - `vue-router` must be installed; the page uid is read from `$route.params.uid`, and a navigation guard warns about unsaved changes.
8
+ - The host app must provide the scaffold injections: `alert`, `appStyle`, `confirm`, `useSocket`, `toast` (all provided by the standard scaffold app setup).
9
+ - A preview route (returned by the server as `previewHost`, e.g. `/web-preview`) that renders the page inside the iframe and implements the iframe message protocol below.
10
+
11
+ ## Props
12
+ | Property | Type | Default | Description |
13
+ |----------|------|---------|-------------|
14
+ | controller | String | `undefined` | Socket controller namespace. Every server call is prefixed with it, e.g. `web-page.open` |
15
+ | presetKey | String | `undefined` | Key used to persist UI state (tab index, panel widths, zoom level…) via `${controller}.preset` |
16
+ | canManageLayout | Boolean | `false` | Whether the user may manage layouts |
17
+ | excludeComponents | Array | `[]` | Component `type` strings to hide from the component selector |
18
+ | moreComponents | Array | `[]` | Extra component definitions added to the selector (`{ type, name, group, props }`) |
19
+ | uploadConfig | Object | `undefined` | Axios-style config (`{ method, url, data }`) used to upload images when `uploadImageFn` is not given |
20
+ | uploadImageFn | Function | `undefined` | `(image, extra?, opt?) => Promise` — custom image upload handler; takes precedence over `uploadConfig` |
21
+ | useChat | Boolean | `false` | Shows the AI chat tab; requires a `${controller}.chat` handler on the server |
22
+ | useDatasource | Array | `undefined` | Datasource support tuple; the second element is the socket path used to load datasource data (the Datasource tab is currently disabled) |
23
+
24
+ ## Events
25
+ | Event | Payload | Description |
26
+ |-------|---------|-------------|
27
+ | close | - | Emitted when the close (✕) button is clicked. The parent decides where to navigate |
28
+ | unmount | `{ uid }` | Emitted when the builder unmounts, with the page uid |
29
+
30
+ ## Methods
31
+ | Method | Parameters | Description |
32
+ |--------|------------|-------------|
33
+ | getPage | - | Returns the current (reactive) page object |
34
+
35
+ ## Server contract
36
+ The widget expects these socket paths on `controller` (base controller paths included):
37
+
38
+ | Path | Payload | Returns |
39
+ |------|---------|---------|
40
+ | `${controller}.open` | `{ uid }` | `{ page, previewHost, debugMode? }` — `page.app.hosts[0].name` is used to build the public URL |
41
+ | `${controller}.save` | `{ ...page, layout }` | - |
42
+ | `${controller}.load-layouts` | `{}` | Array of layout objects |
43
+ | `${controller}.create-layout` | layout | The created layout |
44
+ | `${controller}.load-templates` | - | `{ items }` — component templates for the selector |
45
+ | `${controller}.subscribe` / `.unsubscribe` | `{ name:'page' }` | - |
46
+ | `${controller}.preset` | `{ key }` / `{ key, config }` | Persisted UI config |
47
+ | `${controller}.chat` | `{ key, prompt, attachments, pageId, input }` | `{ messages }` — only needed when `useChat` is set |
48
+
49
+ ## Preview iframe protocol
50
+ Messages are exchanged with the preview iframe via `postMessage`.
51
+
52
+ Sent to the iframe:
53
+ | Message | Description |
54
+ |---------|-------------|
55
+ | `{ action:'update', page }` | Full page JSON (headers, footers, components, stylesheet, data, editMode). Throttled; sent on every page/layout change |
56
+ | `{ action:'select', uid }` | Scrolls/highlights the component with `uid` |
57
+ | `{ action:'reload' }` | Reloads the preview |
58
+
59
+ Received from the iframe:
60
+ | Message | Description |
61
+ |---------|-------------|
62
+ | `{ type:'mounted' }` | Preview is ready; triggers the first update |
63
+ | `{ type:'component-click', uid }` | Selects the clicked component in the editor |
64
+ | `{ type:'setProp', uid, value }` | Merges `value` into the component's props (inline editing) |
65
+ | `{ type:'setSubAction', value }` | Forwarded to the active setting component |
66
+
67
+ ## Provided injections
68
+ `*Setting` components rendered in the right panel can inject: `imageUrl`, `imageSrc`, `store`, `postIframe`, `getPage`, `uploadImage`, `openComponentSelector`, `setUid`, `openRightPane2`, `getRightPane`, `pageHistory`, `duplicate`.
69
+
70
+ Each component type `Foo` resolves its setting panel as a globally registered `FooSetting` component.
71
+
72
+ ## Keyboard shortcuts
73
+ | Shortcut | Action |
74
+ |----------|--------|
75
+ | Alt+1 | Mobile view |
76
+ | Alt+2 | Tablet view (`md:`) |
77
+ | Alt+3 | Desktop view (`xl:`) |
78
+ | Alt+4 | Wide view (`2xl:`) |
79
+
80
+ ## Samples
81
+
82
+ ### Page builder page (see `admin/src/pages/WebPageEdit.vue`)
83
+ ```vue
84
+ <template>
85
+ <div class="flex-1 flex flex-col">
86
+ <WebPageBuilder class="flex-1"
87
+ controller="web-page"
88
+ preset-key="web-page"
89
+ :more-components="components"
90
+ :upload-image-fn="uploadImage"
91
+ :use-chat="true"
92
+ @close="$router.push({ name:'WebPage' })" />
93
+ </div>
94
+ </template>
95
+
96
+ <script setup>
97
+ import {inject, ref} from 'vue'
98
+
99
+ const socket = inject('useSocket')()
100
+
101
+ const components = ref([
102
+ { type:'SaasRegister', name:'SAAS Register', group:'SAAS', props:{ items:[] }},
103
+ ])
104
+
105
+ async function uploadImage(image){
106
+ return socket.send('web-page.upload-image', { image })
107
+ }
108
+ </script>
109
+ ```
110
+
111
+ The route must carry the page uid, e.g.:
112
+ ```js
113
+ {
114
+ name: 'WebPageEdit',
115
+ path: 'web-page/:uid',
116
+ component: () => import('./pages/WebPageEdit.vue')
117
+ }
118
+ ```
119
+
120
+ ### Upload via config instead of a function
121
+ ```vue
122
+ <WebPageBuilder controller="web-page"
123
+ :upload-config="{ method:'post', url:'/api/upload' }"
124
+ @close="$router.back()" />
125
+ ```