@fui-org/fui-cli 2.1.3 → 2.2.0

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 (219) hide show
  1. package/README.md +27 -7
  2. package/dist/fui-qrb1j9jz.js +397 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/fui/SKILL.md +143 -38
  6. package/skills/fui/agents/fui-builder.md +62 -0
  7. package/skills/fui/agents/fui-tester.md +77 -0
  8. package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
  9. package/skills/{fui-skill → fui}/examples/component.vue +162 -162
  10. package/skills/{fui-skill → fui}/references/INDEX.md +5 -4
  11. package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
  12. package/skills/{fui-skill → fui}/references/component-design.md +2 -2
  13. package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
  14. package/skills/{fui-skill → fui}/references/component-table.md +287 -287
  15. package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
  16. package/skills/{fui-skill → fui}/references/components-display.md +141 -141
  17. package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
  18. package/skills/{fui-skill → fui}/references/components-input.md +335 -335
  19. package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
  20. package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
  21. package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
  22. package/skills/{fui-skill → fui}/references/default-function.md +456 -456
  23. package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
  24. package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
  25. package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
  26. package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
  27. package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
  28. package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
  29. package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
  30. package/skills/{fui-skill → fui}/references/new-session.md +1 -2
  31. package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
  32. package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
  33. package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
  34. package/skills/{fui-skill → fui}/references/project-config.md +303 -303
  35. package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
  36. package/skills/{fui-skill → fui}/references/script-map.md +262 -262
  37. package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
  38. package/skills/{fui-skill → fui}/references/system-design.md +2 -2
  39. package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
  40. package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
  41. package/skills/fui/references/team-run.md +141 -0
  42. package/skills/fui/references/tools-registry.md +211 -0
  43. package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
  44. package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
  45. package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
  46. package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
  47. package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
  48. package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
  49. package/skills/{fui-skill → fui}/references/verification.md +11 -11
  50. package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
  51. package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
  52. package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
  53. package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
  54. package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
  55. package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
  56. package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
  57. package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
  58. package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
  59. package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
  60. package/skills/fui-target/hooks/register.tsx +2 -0
  61. package/skills/fui-target/types/index.d.ts +1 -0
  62. package/dist/fui-hk5frcn2.js +0 -381
  63. package/skills/fui-skill/SKILL.md +0 -139
  64. package/skills/fui-skill/references/tools-registry.md +0 -84
  65. /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
  66. /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
  67. /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
  68. /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
  69. /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
  70. /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
  71. /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
  72. /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
  73. /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
  74. /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
  75. /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
  76. /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
  77. /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
  78. /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
  79. /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
  80. /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
  81. /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
  82. /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
  83. /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
  84. /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
  85. /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
  86. /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
  87. /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
  88. /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
  89. /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
  90. /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
  91. /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
  92. /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
  93. /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
  94. /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
  95. /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
  96. /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
  97. /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
  98. /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
  99. /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
  100. /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
  101. /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
  102. /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
  103. /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
  104. /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
  105. /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
  106. /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
  107. /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
  108. /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
  109. /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
  110. /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
  111. /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
  112. /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
  113. /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
  114. /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
  115. /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
  116. /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
  117. /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
  118. /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
  119. /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
  120. /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
  121. /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
  122. /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
  123. /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
  124. /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
  125. /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
  126. /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
  127. /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
  128. /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
  129. /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
  130. /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
  131. /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
  132. /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
  133. /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
  134. /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
  135. /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
  136. /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
  137. /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
  138. /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
  139. /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
  140. /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
  141. /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
  142. /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
  143. /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
  144. /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
  145. /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
  146. /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
  147. /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
  148. /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
  149. /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
  150. /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
  151. /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
  152. /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
  153. /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
  154. /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
  155. /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
  156. /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
  157. /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
  158. /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
  159. /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
  160. /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
  161. /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
  162. /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
  163. /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
  164. /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
  165. /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
  166. /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
  167. /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
  168. /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
  169. /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
  170. /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
  171. /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
  172. /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
  173. /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
  174. /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
  175. /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
  176. /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
  177. /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
  178. /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
  179. /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
  180. /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
  181. /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
  182. /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
  183. /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
  184. /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
  185. /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
  186. /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
  187. /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
  188. /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
  189. /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
  190. /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
  191. /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
  192. /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
  193. /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
  194. /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
  195. /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
  196. /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
  197. /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
  198. /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
  199. /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
  200. /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
  201. /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
  202. /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
  203. /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
  204. /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
  205. /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
  206. /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
  207. /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
  208. /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
  209. /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
  210. /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
  211. /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
  212. /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
  213. /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
  214. /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
  215. /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
  216. /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
  217. /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
  218. /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
  219. /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
@@ -1,335 +1,335 @@
1
- # FUI Input & Interaction Components
2
-
3
- > Owns: **input components: `f-date`, `f-time`, `f-search`, `f-radiobox`, `f-button`, `f-menu`, upload**.
4
-
5
- Components for input and user interaction. Prefix/binding rules: [components-display.md](components-display.md).
6
-
7
- ## f-radiobox
8
-
9
- - Props (source): `label`, `items`, `itemValue`, `itemText`
10
- - Emit: `input` on change
11
-
12
- ```json
13
- {
14
- "el": "f-radiobox",
15
- "w": "6",
16
- "attr": {
17
- "label": "Loai",
18
- "v-model": "formData.loai",
19
- ":items": "loaiOptions",
20
- "item-value": "value",
21
- "item-text": "text"
22
- }
23
- }
24
- ```
25
-
26
- ## f-menu
27
-
28
- - Props (source): `items`, `label`, `iconText`, `menuAttr`
29
- - Dropdown button with menu items; `label` empty/undefined → icon-only button
30
- - `items`: array of `{ text, visible, color, "icon-text", action, href, target }`
31
- - `action`: FUI action object, run via `runAction`
32
- - `href` + `target`: navigate like `<a>`
33
- - `visible`: show/hide item (default `true`)
34
-
35
- ```json
36
- {
37
- "el": "f-menu",
38
- "w": "3",
39
- "attr": {
40
- "label": "Tac vu",
41
- "icon-text": "mdi-dots-vertical",
42
- ":items": [
43
- { "text": "Xem chi tiết", "icon-text": "mdi-eye", "action": { "CALL": "vueData.viewDetail" } },
44
- { "text": "Xuất Excel", "icon-text": "mdi-file-excel", "action": { "CALL": "vueData.exportExcel" } },
45
- { "text": "Trang chủ", "icon-text": "mdi-home", "href": "/", "target": "_blank" }
46
- ]
47
- }
48
- }
49
- ```
50
-
51
- ## f-search
52
-
53
- - Props (source): `api`, `apiData`, `items`
54
- - Emit: `input` when selection changes
55
- - Debounced API search (500ms); keywords < 2 chars ignored
56
-
57
- ```json
58
- {
59
- "el": "f-search",
60
- "w": "6",
61
- "attr": {
62
- "v-model": "filter.userID",
63
- "api": "/api/user/search",
64
- ":api-data": { "Keyword": "TEXT", "GroupID": "vueData.groupID" },
65
- "item-text": "UserName",
66
- "item-value": "UserID"
67
- }
68
- }
69
- ```
70
-
71
- ## f-button
72
-
73
- - Props (source): `label`, `checkvalid`, `ctrlhotkey`, `iconText`, `action`, `includeData`
74
- - Debounced click (500ms, leading); validates form when `checkvalid=true`
75
- - `$attrs.target == 'dialog'` → opens a window from attrs (`wid`, `title`, `url`, `onclose`)
76
-
77
- ```json
78
- {
79
- "el": "f-button",
80
- "w": "3",
81
- "attr": {
82
- "label": "Luu",
83
- "color": "primary",
84
- ":action": { "CALL": "handleSubmit" }
85
- }
86
- }
87
- ```
88
-
89
- ## f-date
90
-
91
- - Props (source): `value`, `label`, `dateAdd`, `required`
92
- - Emit: `input` with normalized date (or `null`)
93
- - Typed mask + picker; accepts many input formats on init; `dateAdd` sets a date relative to today
94
-
95
- ```json
96
- {
97
- "el": "f-date",
98
- "w": "6",
99
- "attr": {
100
- "v-model": "filter.fromDate",
101
- "label": "Tu ngay",
102
- ":required": false,
103
- ":date-add": -7
104
- }
105
- }
106
- ```
107
-
108
- ## f-time
109
-
110
- - Props (source): `value`, `label`, `timeAdd`, `required`
111
- - Emit: `input` with `HH:mm` (or `null`)
112
- - Typed mask + time picker
113
-
114
- ```json
115
- {
116
- "el": "f-time",
117
- "w": "6",
118
- "attr": {
119
- "v-model": "formData.startTime",
120
- "label": "Gio bat dau",
121
- ":required": false,
122
- ":time-add": 30
123
- }
124
- }
125
- ```
126
-
127
- ## f-time-counter
128
-
129
- - Props (source): `value`, `type`, `labelFormat`, `format`
130
- - `value`: seconds (Number) or datetime string (`"yyyy-MM-dd HH:mm:ss"`)
131
- - `type`: `0` countdown with full days/hours/minutes/seconds · `1` countdown showing total remaining seconds only · `2` real-time clock (current time)
132
- - `format`: `{ label, day, hour, minute, second }` — unit labels
133
- - Emit: `input` (remaining seconds) when counting from seconds (Number value); `time-end` when timer hits 0
134
-
135
- ```json
136
- {
137
- "el": "f-time-counter",
138
- "w": "4",
139
- "attr": {
140
- ":value": 300,
141
- ":type": 0,
142
- ":format": { "label": "Còn lại: ", "day": " ngày", "hour": " giờ", "minute": " phút", "second": " giây" }
143
- }
144
- }
145
- ```
146
-
147
- ## f-qrcode
148
-
149
- - Props (source): `value`, `logo`, `color`, `size`
150
- - Renders a QR image from value
151
-
152
- ```json
153
- {
154
- "el": "f-qrcode",
155
- "w": "4",
156
- "attr": {
157
- "v-model": "formData.qrText",
158
- ":size": 200,
159
- "color": "#1e90ff"
160
- }
161
- }
162
- ```
163
-
164
- ## f-qrcode-reader
165
-
166
- - Props (source): `value`, `label`, `width`
167
- - Emit: `input` (closes dialog), `update` (decoded content)
168
-
169
- ```json
170
- {
171
- "el": "f-qrcode-reader",
172
- "w": "12",
173
- "attr": {
174
- "v-model": "scanDialog",
175
- "label": "Doc ma QR",
176
- "v-on:update": "CALL(vueData.handleScanResult, { item: $event })"
177
- }
178
- }
179
- ```
180
-
181
- ## f-image-update
182
-
183
- - Props (source): `value`, `label`, `title`, `apiUpload`, `iconText`, `imageType`, `size`, `quality`, `dialogWidth`, `dialogHeight`, `imageBoxAttr`, `imageAttr`, `buttonAttr`, `croperAttr`
184
- - Emit: `update` after successful upload (`{ returnData, imageData }`)
185
-
186
- ```json
187
- {
188
- "el": "f-image-update",
189
- "w": "4",
190
- "attr": {
191
- "v-model": "formData.avatar",
192
- "label": "Anh dai dien",
193
- "api-upload": "/api/upload/avatar",
194
- ":dialog-width": 600,
195
- ":dialog-height": 420
196
- }
197
- }
198
- ```
199
-
200
- ## f-file-upload
201
-
202
- - Props (source): `label`, `url`, `autoclose`, `iconText`, `filters`, `resize`, `onclose`
203
- - Emit: `input` on upload complete
204
- - Uses Plupload; uploads on file select; runs `onclose` action when the dialog closes
205
-
206
- ```json
207
- {
208
- "el": "f-file-upload",
209
- "w": "4",
210
- "attr": {
211
- "label": "Tai tep",
212
- "url": "/api/upload/file",
213
- ":filters": {
214
- "max_file_size": "20mb",
215
- "mime_types": [{ "title": "PDF", "extensions": "pdf" }]
216
- },
217
- ":onclose": { "CALL": "handleUploadClosed" }
218
- }
219
- }
220
- ```
221
-
222
- ## f-excel-reader
223
-
224
- Button: reads Excel/CSV → `v-model` (array of objects, or of arrays per `:header`). XLSX lazy-loads on first pick; **no import** in module.json.
225
-
226
- **Outputs:**
227
-
228
- - `v-model` — parsed data array
229
- - `load` — metadata `{ rows, rowCount, headers, sheetName, sheetNames, fileName }`
230
- - `error` — `{ message, fileName }` when file too large, read fails, or XLSX load times out
231
- - `action-error` — `{ message, fileName }` when the action callback throws (Excel data still valid)
232
-
233
- **Props — display:**
234
-
235
- | Prop | Default | Description |
236
- | ----------- | ------------------------------------------------------------ | ------------------------------ |
237
- | `label` | `'Load Excel'` | Button label |
238
- | `icon-text` | `'mdi-grid'` | MDI icon; `null`/`''` hides it |
239
- | `accept` | `.xlsx,.xlsb,.xlsm,.xls,.csv,.txt,.ods,.dbf,.prn,.html,.htm` | Accepted file types |
240
-
241
- **Props — reading:**
242
-
243
- | Prop | Default | Description |
244
- | ------------- | -------------- | ------------------------------------------------------------- |
245
- | `:header` | `null` | Decides the object keys in the result (table below) |
246
- | `:raw` | `false` | `false` = parse numbers and dates; `true` = keep raw strings |
247
- | `date-format` | `'dd/mm/yyyy'` | Date format when `:raw="false"` |
248
- | `:defval` | `''` | Value for empty cells |
249
- | `:sheet` | `0` | Sheet to read — number (0-based index) or sheet name (string) |
250
- | `:skip-rows` | `0` | Skip N leading rows before header/data |
251
-
252
- **`:header` values:**
253
-
254
- | Value | Result |
255
- | ------------------ | ------------------------------------------------------------- |
256
- | `null` _(default)_ | First sheet row as keys: `[{MSSV:"1234", HoTen:"An"}]` |
257
- | `1` | Array of arrays, no keys: `[["MSSV","HoTen"],["1234","An"]]` |
258
- | `"A"` | Excel column letters as keys: `[{A:"MSSV", B:"HoTen"}]` |
259
- | `["ma","ten"]` | Your own key names by column order (file header row not used) |
260
-
261
- ⚠️ **`:sheet` trap — wrong name does NOT error:**
262
-
263
- - Nonexistent **name** → **silently reads the first sheet** (wrong data, no sign).
264
- - **Number** beyond sheet count → `Không tìm thấy sheet: N` via `error`.
265
-
266
- With a name, compare `load`.`sheetName` to the expected name.
267
-
268
- **Props — limits & timeout:**
269
-
270
- | Prop | Default | Description |
271
- | --------------- | ------- | ------------------------------------------------------------- |
272
- | `:max-size` | `20` | File size limit, **in MB** (name has no "MB"); `0` = no limit |
273
- | `:load-timeout` | `15000` | XLSX library load timeout (ms) |
274
-
275
- Unparseable `:max-size` → **20 MB**, not unlimited; no limit = exactly number `0`.
276
-
277
- **Prop — callback:**
278
-
279
- | Prop | Description |
280
- | --------- | ------------------------------------------------------------------------------------------------ |
281
- | `:action` | Action object run after parsing, once `v-model` has the data — usually `{ "CALL": "tenAction" }` |
282
-
283
- **Error boundary — reading and action are separate stages:**
284
-
285
- - `action` runs after `v-model` has the data (`$nextTick`); read `vueData.excelRows` directly.
286
- - After reading, **nothing becomes `error`** — action errors → `action-error`, parsed data kept, no rollback. `error` ⇒ reading failed; `action-error` ⇒ processing failed.
287
- - Clicks while parsing are ignored; a new file picked before the old finishes discards the old result (never overwrites the new).
288
-
289
- **Basic — header from first row (default):**
290
-
291
- ```json
292
- {
293
- "el": "f-excel-reader",
294
- "w": "4",
295
- "attr": {
296
- "label": "Nhập từ Excel",
297
- "icon-text": "mdi-microsoft-excel",
298
- ":action": { "CALL": "cmdImportExcel" },
299
- "v-model": "excelRows"
300
- }
301
- }
302
- ```
303
-
304
- **Sheet by name, skip 2 rows, keep raw strings:**
305
-
306
- ```json
307
- {
308
- "el": "f-excel-reader",
309
- "w": "4",
310
- "attr": {
311
- "label": "Nhập DanhSach",
312
- ":sheet": "DanhSach",
313
- ":skip-rows": 2,
314
- ":raw": true,
315
- ":action": { "CALL": "cmdImportExcel" },
316
- "v-model": "excelRows"
317
- }
318
- }
319
- ```
320
-
321
- **Custom column names, 5 MB limit:**
322
-
323
- ```json
324
- {
325
- "el": "f-excel-reader",
326
- "w": "4",
327
- "attr": {
328
- "label": "Nhập Excel",
329
- ":header": ["mssv", "hoTen", "diemTB"],
330
- ":max-size": 5,
331
- ":action": { "CALL": "cmdImportExcel" },
332
- "v-model": "excelRows"
333
- }
334
- }
335
- ```
1
+ # FUI Input & Interaction Components
2
+
3
+ > Owns: **input components: `f-date`, `f-time`, `f-search`, `f-radiobox`, `f-button`, `f-menu`, upload**.
4
+
5
+ Components for input and user interaction. Prefix/binding rules: [components-display.md](components-display.md).
6
+
7
+ ## f-radiobox
8
+
9
+ - Props (source): `label`, `items`, `itemValue`, `itemText`
10
+ - Emit: `input` on change
11
+
12
+ ```json
13
+ {
14
+ "el": "f-radiobox",
15
+ "w": "6",
16
+ "attr": {
17
+ "label": "Loai",
18
+ "v-model": "formData.loai",
19
+ ":items": "loaiOptions",
20
+ "item-value": "value",
21
+ "item-text": "text"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## f-menu
27
+
28
+ - Props (source): `items`, `label`, `iconText`, `menuAttr`
29
+ - Dropdown button with menu items; `label` empty/undefined → icon-only button
30
+ - `items`: array of `{ text, visible, color, "icon-text", action, href, target }`
31
+ - `action`: FUI action object, run via `runAction`
32
+ - `href` + `target`: navigate like `<a>`
33
+ - `visible`: show/hide item (default `true`)
34
+
35
+ ```json
36
+ {
37
+ "el": "f-menu",
38
+ "w": "3",
39
+ "attr": {
40
+ "label": "Tac vu",
41
+ "icon-text": "mdi-dots-vertical",
42
+ ":items": [
43
+ { "text": "Xem chi tiết", "icon-text": "mdi-eye", "action": { "CALL": "vueData.viewDetail" } },
44
+ { "text": "Xuất Excel", "icon-text": "mdi-file-excel", "action": { "CALL": "vueData.exportExcel" } },
45
+ { "text": "Trang chủ", "icon-text": "mdi-home", "href": "/", "target": "_blank" }
46
+ ]
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## f-search
52
+
53
+ - Props (source): `api`, `apiData`, `items`
54
+ - Emit: `input` when selection changes
55
+ - Debounced API search (500ms); keywords < 2 chars ignored
56
+
57
+ ```json
58
+ {
59
+ "el": "f-search",
60
+ "w": "6",
61
+ "attr": {
62
+ "v-model": "filter.userID",
63
+ "api": "/api/user/search",
64
+ ":api-data": { "Keyword": "TEXT", "GroupID": "vueData.groupID" },
65
+ "item-text": "UserName",
66
+ "item-value": "UserID"
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## f-button
72
+
73
+ - Props (source): `label`, `checkvalid`, `ctrlhotkey`, `iconText`, `action`, `includeData`
74
+ - Debounced click (500ms, leading); validates form when `checkvalid=true`
75
+ - `$attrs.target == 'dialog'` → opens a window from attrs (`wid`, `title`, `url`, `onclose`)
76
+
77
+ ```json
78
+ {
79
+ "el": "f-button",
80
+ "w": "3",
81
+ "attr": {
82
+ "label": "Luu",
83
+ "color": "primary",
84
+ ":action": { "CALL": "handleSubmit" }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## f-date
90
+
91
+ - Props (source): `value`, `label`, `dateAdd`, `required`
92
+ - Emit: `input` with normalized date (or `null`)
93
+ - Typed mask + picker; accepts many input formats on init; `dateAdd` sets a date relative to today
94
+
95
+ ```json
96
+ {
97
+ "el": "f-date",
98
+ "w": "6",
99
+ "attr": {
100
+ "v-model": "filter.fromDate",
101
+ "label": "Tu ngay",
102
+ ":required": false,
103
+ ":date-add": -7
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## f-time
109
+
110
+ - Props (source): `value`, `label`, `timeAdd`, `required`
111
+ - Emit: `input` with `HH:mm` (or `null`)
112
+ - Typed mask + time picker
113
+
114
+ ```json
115
+ {
116
+ "el": "f-time",
117
+ "w": "6",
118
+ "attr": {
119
+ "v-model": "formData.startTime",
120
+ "label": "Gio bat dau",
121
+ ":required": false,
122
+ ":time-add": 30
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## f-time-counter
128
+
129
+ - Props (source): `value`, `type`, `labelFormat`, `format`
130
+ - `value`: seconds (Number) or datetime string (`"yyyy-MM-dd HH:mm:ss"`)
131
+ - `type`: `0` countdown with full days/hours/minutes/seconds · `1` countdown showing total remaining seconds only · `2` real-time clock (current time)
132
+ - `format`: `{ label, day, hour, minute, second }` — unit labels
133
+ - Emit: `input` (remaining seconds) when counting from seconds (Number value); `time-end` when timer hits 0
134
+
135
+ ```json
136
+ {
137
+ "el": "f-time-counter",
138
+ "w": "4",
139
+ "attr": {
140
+ ":value": 300,
141
+ ":type": 0,
142
+ ":format": { "label": "Còn lại: ", "day": " ngày", "hour": " giờ", "minute": " phút", "second": " giây" }
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## f-qrcode
148
+
149
+ - Props (source): `value`, `logo`, `color`, `size`
150
+ - Renders a QR image from value
151
+
152
+ ```json
153
+ {
154
+ "el": "f-qrcode",
155
+ "w": "4",
156
+ "attr": {
157
+ "v-model": "formData.qrText",
158
+ ":size": 200,
159
+ "color": "#1e90ff"
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## f-qrcode-reader
165
+
166
+ - Props (source): `value`, `label`, `width`
167
+ - Emit: `input` (closes dialog), `update` (decoded content)
168
+
169
+ ```json
170
+ {
171
+ "el": "f-qrcode-reader",
172
+ "w": "12",
173
+ "attr": {
174
+ "v-model": "scanDialog",
175
+ "label": "Doc ma QR",
176
+ "v-on:update": "CALL(vueData.handleScanResult, { item: $event })"
177
+ }
178
+ }
179
+ ```
180
+
181
+ ## f-image-update
182
+
183
+ - Props (source): `value`, `label`, `title`, `apiUpload`, `iconText`, `imageType`, `size`, `quality`, `dialogWidth`, `dialogHeight`, `imageBoxAttr`, `imageAttr`, `buttonAttr`, `croperAttr`
184
+ - Emit: `update` after successful upload (`{ returnData, imageData }`)
185
+
186
+ ```json
187
+ {
188
+ "el": "f-image-update",
189
+ "w": "4",
190
+ "attr": {
191
+ "v-model": "formData.avatar",
192
+ "label": "Anh dai dien",
193
+ "api-upload": "/api/upload/avatar",
194
+ ":dialog-width": 600,
195
+ ":dialog-height": 420
196
+ }
197
+ }
198
+ ```
199
+
200
+ ## f-file-upload
201
+
202
+ - Props (source): `label`, `url`, `autoclose`, `iconText`, `filters`, `resize`, `onclose`
203
+ - Emit: `input` on upload complete
204
+ - Uses Plupload; uploads on file select; runs `onclose` action when the dialog closes
205
+
206
+ ```json
207
+ {
208
+ "el": "f-file-upload",
209
+ "w": "4",
210
+ "attr": {
211
+ "label": "Tai tep",
212
+ "url": "/api/upload/file",
213
+ ":filters": {
214
+ "max_file_size": "20mb",
215
+ "mime_types": [{ "title": "PDF", "extensions": "pdf" }]
216
+ },
217
+ ":onclose": { "CALL": "handleUploadClosed" }
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## f-excel-reader
223
+
224
+ Button: reads Excel/CSV → `v-model` (array of objects, or of arrays per `:header`). XLSX lazy-loads on first pick; **no import** in module.json.
225
+
226
+ **Outputs:**
227
+
228
+ - `v-model` — parsed data array
229
+ - `load` — metadata `{ rows, rowCount, headers, sheetName, sheetNames, fileName }`
230
+ - `error` — `{ message, fileName }` when file too large, read fails, or XLSX load times out
231
+ - `action-error` — `{ message, fileName }` when the action callback throws (Excel data still valid)
232
+
233
+ **Props — display:**
234
+
235
+ | Prop | Default | Description |
236
+ | ----------- | ------------------------------------------------------------ | ------------------------------ |
237
+ | `label` | `'Load Excel'` | Button label |
238
+ | `icon-text` | `'mdi-grid'` | MDI icon; `null`/`''` hides it |
239
+ | `accept` | `.xlsx,.xlsb,.xlsm,.xls,.csv,.txt,.ods,.dbf,.prn,.html,.htm` | Accepted file types |
240
+
241
+ **Props — reading:**
242
+
243
+ | Prop | Default | Description |
244
+ | ------------- | -------------- | ------------------------------------------------------------- |
245
+ | `:header` | `null` | Decides the object keys in the result (table below) |
246
+ | `:raw` | `false` | `false` = parse numbers and dates; `true` = keep raw strings |
247
+ | `date-format` | `'dd/mm/yyyy'` | Date format when `:raw="false"` |
248
+ | `:defval` | `''` | Value for empty cells |
249
+ | `:sheet` | `0` | Sheet to read — number (0-based index) or sheet name (string) |
250
+ | `:skip-rows` | `0` | Skip N leading rows before header/data |
251
+
252
+ **`:header` values:**
253
+
254
+ | Value | Result |
255
+ | ------------------ | ------------------------------------------------------------- |
256
+ | `null` _(default)_ | First sheet row as keys: `[{MSSV:"1234", HoTen:"An"}]` |
257
+ | `1` | Array of arrays, no keys: `[["MSSV","HoTen"],["1234","An"]]` |
258
+ | `"A"` | Excel column letters as keys: `[{A:"MSSV", B:"HoTen"}]` |
259
+ | `["ma","ten"]` | Your own key names by column order (file header row not used) |
260
+
261
+ ⚠️ **`:sheet` trap — wrong name does NOT error:**
262
+
263
+ - Nonexistent **name** → **silently reads the first sheet** (wrong data, no sign).
264
+ - **Number** beyond sheet count → `Không tìm thấy sheet: N` via `error`.
265
+
266
+ With a name, compare `load`.`sheetName` to the expected name.
267
+
268
+ **Props — limits & timeout:**
269
+
270
+ | Prop | Default | Description |
271
+ | --------------- | ------- | ------------------------------------------------------------- |
272
+ | `:max-size` | `20` | File size limit, **in MB** (name has no "MB"); `0` = no limit |
273
+ | `:load-timeout` | `15000` | XLSX library load timeout (ms) |
274
+
275
+ Unparseable `:max-size` → **20 MB**, not unlimited; no limit = exactly number `0`.
276
+
277
+ **Prop — callback:**
278
+
279
+ | Prop | Description |
280
+ | --------- | ------------------------------------------------------------------------------------------------ |
281
+ | `:action` | Action object run after parsing, once `v-model` has the data — usually `{ "CALL": "tenAction" }` |
282
+
283
+ **Error boundary — reading and action are separate stages:**
284
+
285
+ - `action` runs after `v-model` has the data (`$nextTick`); read `vueData.excelRows` directly.
286
+ - After reading, **nothing becomes `error`** — action errors → `action-error`, parsed data kept, no rollback. `error` ⇒ reading failed; `action-error` ⇒ processing failed.
287
+ - Clicks while parsing are ignored; a new file picked before the old finishes discards the old result (never overwrites the new).
288
+
289
+ **Basic — header from first row (default):**
290
+
291
+ ```json
292
+ {
293
+ "el": "f-excel-reader",
294
+ "w": "4",
295
+ "attr": {
296
+ "label": "Nhập từ Excel",
297
+ "icon-text": "mdi-microsoft-excel",
298
+ ":action": { "CALL": "cmdImportExcel" },
299
+ "v-model": "excelRows"
300
+ }
301
+ }
302
+ ```
303
+
304
+ **Sheet by name, skip 2 rows, keep raw strings:**
305
+
306
+ ```json
307
+ {
308
+ "el": "f-excel-reader",
309
+ "w": "4",
310
+ "attr": {
311
+ "label": "Nhập DanhSach",
312
+ ":sheet": "DanhSach",
313
+ ":skip-rows": 2,
314
+ ":raw": true,
315
+ ":action": { "CALL": "cmdImportExcel" },
316
+ "v-model": "excelRows"
317
+ }
318
+ }
319
+ ```
320
+
321
+ **Custom column names, 5 MB limit:**
322
+
323
+ ```json
324
+ {
325
+ "el": "f-excel-reader",
326
+ "w": "4",
327
+ "attr": {
328
+ "label": "Nhập Excel",
329
+ ":header": ["mssv", "hoTen", "diemTB"],
330
+ ":max-size": 5,
331
+ ":action": { "CALL": "cmdImportExcel" },
332
+ "v-model": "excelRows"
333
+ }
334
+ }
335
+ ```