75 Commits

Author SHA1 Message Date
2b74c29556 feat: format import
All checks were successful
/ test (push) Successful in 18s
/ surge (push) Successful in 36s
2025-02-18 15:42:14 +08:00
693df86068 feat: 更新依赖项和Node版本以提升兼容性
All checks were successful
/ test (push) Successful in 20s
/ surge (push) Successful in 36s
2025-02-18 15:27:14 +08:00
6bb5de5330 feat: 更新文件上传项组件的边框颜色样式
All checks were successful
/ test (push) Successful in -4s
/ surge (push) Successful in 42s
2025-01-02 14:24:21 +08:00
33c3e5c31d feat: 移除消息组件的简单变体并添加样式调整
All checks were successful
/ test (push) Successful in 21s
/ surge (push) Successful in 24s
2025-01-02 11:27:35 +08:00
17c4afefd9 feat: 为文件上传组件添加自定义图片插槽支持
All checks were successful
/ test (push) Successful in 53s
/ surge (push) Successful in 52s
2025-01-02 09:55:58 +08:00
7277e8e392 feat: 更新文件上传组件以支持空文件列表处理
All checks were successful
/ test (push) Successful in 20s
/ surge (push) Successful in 23s
2024-12-31 17:04:23 +08:00
86f47b5113 feat: 为文件上传组件添加禁用状态支持
All checks were successful
/ test (push) Successful in 21s
/ surge (push) Successful in 22s
2024-12-31 16:31:04 +08:00
f94bec1118 refactor: 优化 PFileUpload 组件的结构,移除多余的包装 div
All checks were successful
/ test (push) Successful in 19s
/ surge (push) Successful in 42s
2024-12-31 15:28:18 +08:00
6a1a7d2d47 feat: 添加 PFileUpload 组件及相关类型定义,支持文件上传功能
All checks were successful
/ test (push) Successful in 3s
/ surge (push) Successful in 55s
2024-12-31 15:23:51 +08:00
ab39617e7b feat: 为多个组件添加帮助信息支持
All checks were successful
/ test (push) Successful in 23s
/ surge (push) Successful in 14s
2024-12-30 16:16:24 +08:00
7a1712807f feat: 为 PInputText 组件添加帮助信息支持
All checks were successful
/ test (push) Successful in 23s
/ surge (push) Successful in 41s
2024-12-30 14:08:11 +08:00
e85666fec1 fix: 修复 PSelect 组件的 invalid 属性逻辑
All checks were successful
/ test (push) Successful in 4s
/ surge (push) Successful in 42s
2024-12-24 12:15:30 +08:00
b8280cecdd feat: 为 PSelect 组件添加动态边框颜色样式
All checks were successful
/ test (push) Successful in 23s
/ surge (push) Successful in 16s
2024-12-24 12:14:34 +08:00
28ff670dc5 feat: 恢复 PCascadeSelect 组件中的 schemaMemoKey 配置
Some checks failed
/ test (push) Successful in 4s
/ surge (push) Failing after 2m49s
2024-12-23 18:32:07 +08:00
a3e6509783 feat: 添加 PCascadeSelect 组件,更新相关配置和依赖
Some checks failed
/ test (push) Successful in 3s
/ surge (push) Failing after 2m27s
2024-12-23 18:25:10 +08:00
70a9aa5b79 feat: 添加 PDatePicker 组件
Some checks failed
/ test (push) Successful in 22s
/ surge (push) Failing after 2m22s
2024-12-23 16:57:02 +08:00
c1c8e470fd feat: 更新 PSelect 组件,修改 schemaMemoKey 以优化性能
All checks were successful
/ test (push) Successful in 19s
/ surge (push) Successful in 48s
2024-12-06 16:32:23 +08:00
c3bdd9ba9a feat: 更新 PSelect 组件,优化类型定义
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 33s
2024-12-06 16:29:27 +08:00
9b78830313 feat: 更新 PSelect 组件,添加 value 插槽支持,优化选项加载和输入交互体验
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 37s
2024-12-06 16:21:41 +08:00
c4afb0342e feat: 更新 formkit 配置,移除不必要的插件和组件,优化代码结构
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 31s
2024-12-06 15:10:20 +08:00
a6ccc16adb feat: 更新 PSelect 组件,支持异步选项加载并优化配置,调整样式以改善用户体验
All checks were successful
/ test (push) Successful in 21s
/ surge (push) Successful in 57s
2024-12-06 15:05:16 +08:00
7b7828d384 feat: 更新 PSelect 组件,支持异步选项加载并优化 blur 事件处理
All checks were successful
/ test (push) Successful in 20s
/ surge (push) Successful in 48s
2024-12-06 13:13:24 +08:00
603c1047fc feat: 在 PSelect 组件中添加 blur 事件处理,优化输入交互体验
All checks were successful
/ test (push) Successful in 20s
/ surge (push) Successful in 48s
2024-12-06 11:23:05 +08:00
a9fb4bc208 暂存。
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 38s
2024-12-06 00:36:06 +08:00
ba4d361700 feat: PSelect 暂存
All checks were successful
/ test (push) Successful in 20s
/ surge (push) Successful in 45s
2024-12-05 19:09:26 +08:00
c2772dcf3a feat: 在 formkit 配置中添加 list 输入类型支持 [no ci] 2024-12-05 15:29:12 +08:00
f516aac5d2 feat: 移除不必要的 actions 导入,优化 form.ts 文件结构
Some checks failed
/ test (push) Failing after 22s
/ surge (push) Successful in 39s
2024-12-04 18:24:54 +08:00
b65b6bf48e feat: 更新 formkit 配置,优化插件结构并修改按钮点击事件逻辑
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 35s
2024-12-04 17:17:48 +08:00
0afb696583 feat: 在 all-custom 组件中添加 group 类型支持并更新按钮功能
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 37s
2024-12-04 16:31:33 +08:00
dc6b45285d feat: 在 all-custom 组件中添加不在表单中的默认值配置
All checks were successful
/ test (push) Successful in 28s
/ surge (push) Successful in 33s
2024-12-04 16:28:41 +08:00
f9d1a5c2e2 feat: 更新 formkit 配置,添加 group 类型支持并优化组件结构
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 34s
2024-12-04 16:18:01 +08:00
8be5378514 feat: 更新 index.html 和 App.vue,优化页面结构和样式
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 41s
2024-12-03 22:34:56 +08:00
45e5396153 feat: 更新多个依赖项版本,确保兼容性和稳定性
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 31s
2024-12-03 21:56:40 +08:00
1260e86c1c feat: 更新 TypeScript 依赖版本为通配符
All checks were successful
/ test (push) Successful in 24s
/ surge (push) Successful in 36s
2024-11-26 23:02:43 +08:00
0d7f3b7dec feat: 更新依赖项版本
Some checks failed
/ test (push) Failing after 19s
/ surge (push) Successful in 43s
2024-11-26 22:58:18 +08:00
884099b2ee feat: 更新表单输入组件的 schemaMemoKey,确保唯一性
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 39s
2024-11-26 15:31:05 +08:00
5d111ead30 feat: 更新表单输入框的默认值,优化表单数据提交
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 35s
2024-11-26 12:52:52 +08:00
9255f7ac9d feat: 更新表单标题为中文并调整表单样式
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 39s
2024-11-26 10:59:05 +08:00
7d850957a6 floatLabel
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 40s
2024-11-26 10:53:12 +08:00
20ce563e32 feat: 添加按钮组件,优化表单提交功能
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 38s
2024-11-26 09:46:30 +08:00
223b460af8 feat: 移除 AllCustomPrimevue 组件,简化 App.vue 结构
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 47s
2024-11-26 09:43:48 +08:00
de0014590e feat: 重构表单按钮部分,优化 createSection 的使用方式
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 50s
2024-11-26 09:28:56 +08:00
8746997d36 格式化代码
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 39s
2024-11-26 09:17:20 +08:00
8a1e055462 feat: 在 all-custom 组件中添加 some-boolean-prop 属性
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 39s
2024-11-25 17:04:52 +08:00
be4adcc2b4 feat: 在表单提交按钮中添加 loading 状态
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 41s
2024-11-25 15:11:34 +08:00
e4907efab5 feat: 优化 PInputPassword 组件,直接使用 SchemaComponent,移除不必要的组件名称定义
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 38s
2024-11-25 15:03:59 +08:00
0f45fcbf25 feat: 更新 formkit 配置,优化组件导入和调试信息,调整提交延迟
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 44s
2024-11-25 15:02:39 +08:00
d7058af64c feat: 更新 all-custom 组件,增强提交功能并优化模板结构
All checks were successful
/ test (push) Successful in 28s
/ surge (push) Successful in 45s
2024-11-25 13:22:39 +08:00
5511685f18 feat: 添加 PInputPassword 组件并在 formkit.config.ts 中注册
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 39s
2024-11-25 12:36:42 +08:00
a2e30f45e4 feat: 调整 formkit.config.ts 中的插件顺序,优化 PInputText 组件的配置
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 39s
2024-11-25 12:09:56 +08:00
7b5a8e6e41 feat: 更新 PInputText 组件,优化组件名称并调整类型声明
Some checks failed
/ test (push) Successful in 26s
/ surge (push) Has been cancelled
2024-11-25 12:09:23 +08:00
3e1995b0de feat: 移除未使用的自定义组件引用,优化 PInputText 组件的结构
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 37s
2024-11-25 11:40:36 +08:00
299a47dc8b feat: 移除不必要的属性,优化 PInputText 组件的代码
All checks were successful
/ test (push) Successful in 27s
/ surge (push) Successful in 47s
2024-11-25 11:39:34 +08:00
ec9bef3fa9 feat: 更新 README 和配置文件,添加 PInputText 组件并优化输入文本组件
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 38s
2024-11-25 11:38:25 +08:00
282559ea17 feat: 移除未使用的表单组件导入,优化代码结构
All checks were successful
/ test (push) Successful in 28s
/ surge (push) Successful in 47s
2024-11-25 10:42:51 +08:00
7bb954e348 feat: 更新表单组件,简化代码结构并添加调试插件
Some checks failed
/ test (push) Failing after 26s
/ surge (push) Successful in 39s
2024-11-25 10:33:01 +08:00
07320f09b0 feat: 更新 Asterisk 插件的导入路径,优化配置文件结构
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 48s
2024-11-25 09:17:33 +08:00
781eb610ce feat: 更新密码输入组件,添加密码强度提示标签
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 47s
2024-11-25 00:26:42 +08:00
742d92d8fd feat: 更新输入文本组件,添加无效状态绑定,重命名值属性为 modelValue
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 40s
2024-11-25 00:13:38 +08:00
fb329d5746 feat: 移除消息组件中的不必要参数,简化文本输入逻辑
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 46s
2024-11-24 23:58:07 +08:00
dc0c135c16 feat: 移除不必要的验证可见性属性,简化表单组件
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 43s
2024-11-24 23:31:00 +08:00
11f787d172 feat: 使用 markRaw 包装消息组件,优化性能
Some checks failed
/ test (push) Failing after 30s
/ surge (push) Successful in 41s
2024-11-24 23:29:52 +08:00
ab5cf852ff feat: 移除未使用的空函数,优化 formkit 配置
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 45s
2024-11-24 23:24:58 +08:00
29db04c54a feat: 重构消息组件,优化表单输入逻辑,移除不必要的依赖
Some checks failed
/ surge (push) Has been cancelled
/ test (push) Has been cancelled
2024-11-24 23:24:41 +08:00
edd6decdbd feat: 调整表单标签样式,修改为中等字体粗细
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 48s
2024-11-24 22:24:57 +08:00
f0098018cf feat: 注释掉 .formkit-outer 样式,暂时禁用边框
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 43s
2024-11-24 22:21:59 +08:00
6519838995 feat: 删除 input-text 组件,添加消息组件,重构表单输入逻辑
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 40s
2024-11-24 22:15:12 +08:00
2aa80123b8 feat: 重构表单输入组件,添加文本输入类型,优化配置和样式
Some checks failed
/ test (push) Failing after 31s
/ surge (push) Successful in 45s
2024-11-24 17:47:02 +08:00
798954d6f3 feat: 优化 all-custom-primevue.vue 表单布局,调整间距和样式
Some checks failed
/ test (push) Failing after 27s
/ surge (push) Successful in 26s
2024-11-23 22:37:25 +08:00
c9601cc680 feat: 优化 App.vue 布局,调整组件间距和宽度设置
Some checks failed
/ test (push) Failing after 15s
/ surge (push) Successful in 42s
2024-11-23 22:33:14 +08:00
a47d6a6a19 feat: 调整 App.vue 中组件的宽度设置,优化布局
Some checks failed
/ test (push) Failing after 14s
/ surge (push) Successful in 47s
2024-11-23 22:21:13 +08:00
b373f330de feat: 优化 App.vue 布局,调整组件排列方式
Some checks failed
/ test (push) Failing after 28s
/ surge (push) Successful in 24s
2024-11-23 22:04:49 +08:00
533a762151 feat: PrimeVue 表单
Some checks failed
/ test (push) Failing after 12s
/ surge (push) Successful in 47s
2024-11-23 22:00:09 +08:00
fa094e68ff feat: 在 CI 工作流中添加对 unocss-fk 分支的支持
All checks were successful
/ test (push) Successful in 26s
/ surge (push) Successful in 24s
2024-11-22 18:05:49 +08:00
0b40d065ca feat: 添加 Tailwind CSS 支持,更新样式文件并移除不必要的文件
All checks were successful
/ test (push) Successful in 27s
2024-11-22 17:59:32 +08:00
43 changed files with 3231 additions and 1172 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
VITE_DEBUG_FORMKIT=true
# https://formkit.com/pro

View File

@ -3,6 +3,7 @@ on:
push:
branches:
- main
- unocss-fk
jobs:
surge:

View File

@ -6,6 +6,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: yanhao98/composite-actions/setup-node-environment@main
- name: deploy
run: |
npx vue-tsc -b
- run: npx vue-tsc -b

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ dist-ssr
# Local Netlify folder
.netlify
components.d.ts

3
.npmrc
View File

@ -1 +1,4 @@
registry=https://nexus.oo1.dev/repository/npm/
use-node-version=22.14.0
shamefully-hoist=true

View File

@ -3,9 +3,12 @@
"source.fixAll": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar",
"editor.defaultFormatter": "Vue.volar"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -1,21 +1,30 @@
# [Vue FormKit Example](https://formkit.com)
## Knowledge
- https://youtu.be/v8Sb1mXDWa8
- [sections](https://formkit.com/inputs/text#sections)
- [sections](https://formkit.com/essentials/inputs#sections)
- [useformkitcontext](https://formkit.com/inputs/form#useformkitcontext)
- [debouncing](https://formkit.com/essentials/inputs#debouncing)
延迟 prop 的默认值是 20 毫秒。然而group 和 list 输入默认使用 0 毫秒,以防止防抖延迟在每个深度级别“累积”。
## Custom Input
- https://formkit.com/guides/export-and-restructure-inputs
- `npx formkit@latest export`
## Example
- https://formkit.com/essentials/examples
## Theme
- https://themes.formkit.com/editor?theme=regenesis
- [Creating a Tailwind CSS theme for FormKit](https://dev.to/andrewboyd/creating-a-tailwind-css-theme-for-formkit-45o4)
- [issues: Tailwind prefix support for regenesis theme](https://github.com/formkit/formkit/issues/1157)
## 3rd Party
- https://github.com/mathsgod/formkit-element

View File

@ -1,39 +0,0 @@
import type { FormKitNode } from "@formkit/core";
const legends = ['checkbox_multi', 'radio_multi', 'repeater', 'transferlist'];
export function addAsteriskPlugin(node: FormKitNode) {
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
node.on('created', () => {
const legendOrLabel = legends.includes(`${node.props.type}${node.props.options ? '_multi' : ''}`) ? 'legend' : 'label';
console.group('[node created]', node.props.label || node.props.submitLabel)
console.debug(`node :>> `, node);
console.debug(`node.props.definition :>> `, node.props.definition);
// if (typeof node.props.definition!.schema === 'function') {
// console.debug(`node.props.definition.schema.call :>> `, node.props.definition!.schema.call(node.props.definition, {}));
// }
console.debug(`legendOrLabel :>> `, legendOrLabel);
console.groupEnd()
if (node.props.definition?.schemaMemoKey) {
node.props.definition.schemaMemoKey += `${node.props.options ? '_multi' : ''}_add_asterisk`;
};
const schemaFn = node.props.definition!.schema!;
node.props.definition!.schema = (sectionsSchema = {}) => {
sectionsSchema[legendOrLabel] = {
children: ['$label', {
$el: 'span',
if: '$state.required',
attrs: {
class: '$classes.asterisk',
},
children: ['*']
}]
}
return typeof schemaFn === 'function' ? schemaFn(sectionsSchema) : schemaFn
}
})
}

View File

@ -1,25 +0,0 @@
import {
checkbox,
form,
group,
list,
number,
range,
submit,
text,
textarea,
} from "@formkit/inputs";
// https://unocss.dev/presets/wind#differences-from-tailwind-css
export const fkLibrary = {
text,
form,
submit,
group,
checkbox,
range,
list,
number,
textarea,
};

View File

@ -0,0 +1,37 @@
import type { FormKitExtendableSchemaRoot, FormKitNode } from '@formkit/core';
const legends = ['checkbox_multi', 'radio_multi', 'repeater', 'transferlist'];
export function addAsteriskPlugin(node: FormKitNode) {
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
node.on('created', () => {
const legendOrLabel = legends.includes(`${node.props.type}${node.props.options ? '_multi' : ''}`)
? 'legend'
: 'label';
if (!node.props.definition) return;
if (node.props.definition.schemaMemoKey) {
node.props.definition.schemaMemoKey += `${node.props.options ? '_multi' : ''}_add_asterisk`;
}
const schemaFn = node.props.definition.schema! as FormKitExtendableSchemaRoot;
node.props.definition!.schema = (sectionsSchema = {}) => {
sectionsSchema[legendOrLabel] = {
children: [
'$label',
{
$el: 'span',
if: '$state.required',
attrs: {
class: '$classes.asterisk',
},
children: ['*'],
},
],
};
return schemaFn(sectionsSchema);
};
});
}

View File

@ -0,0 +1,10 @@
export function debugPlugin(node: import('@formkit/core').FormKitNode) {
if (['button', 'submit', 'hidden', 'group', 'list', 'meta'].includes(node.props.type)) return;
node.on('created', () => {
console.group('[node created]', node.props.label || node.props.submitLabel || node.name);
console.debug(`node :>> `, node);
console.debug(`node.props.definition :>> `, node.props.definition);
console.groupEnd();
});
}

View File

@ -26,23 +26,11 @@ import type { FormKitNode } from '@formkit/core'
* ```
**/
export function rootClasses (sectionName: string, node: FormKitNode): Record<string, boolean> {
const key = `${node.props.type}__${sectionName}`
const semanticKey = `formkit-${sectionName}`
const familyKey = node.props.family ? `family:${node.props.family}__${sectionName}` : ''
const memoKey = `${key}__${familyKey}`
if (!(memoKey in classes)) {
setTimeout(() => {
// console.group(`rootClasses(${sectionName}) ${memoKey}`)
// console.debug(`key :>> `, key);
// console.debug(`semanticKey :>> `, semanticKey);
// console.debug(`familyKey :>> `, familyKey);
// console.debug(`memoKey :>> `, memoKey);
// console.debug(`classes[key] :>> `, classes[key]);
// console.debug(`globals[sectionName] :>> `, globals[sectionName]);
// console.groupEnd()
}, 1000);
const sectionClasses = classes[key] ?? globals[sectionName] ?? {}
sectionClasses[semanticKey] = true
if (familyKey in classes) {
@ -58,7 +46,7 @@ export function rootClasses(sectionName: string, node: FormKitNode): Record<stri
* These classes have already been merged with globals using tailwind-merge
* and are ready to be used directly in the theme.
**/
export const classes: Record<string, Record<string, boolean>> = {
const classes: Record<string, Record<string, boolean>> = {
"family:button__wrapper": {
"group-data-[disabled=true]:grayscale": true
},
@ -3300,7 +3288,7 @@ export const classes: Record<string, Record<string, boolean>> = {
* Globals are merged prior to generating this file — these are included for
* any other non-matching inputs.
**/
export const globals: Record<string, Record<string, boolean>> = {
const globals: Record<string, Record<string, boolean>> = {
"outer": {
"group": true,
"max-w-[20em]": true,

View File

@ -1,58 +1,79 @@
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons'
import type { FormKitOptions } from '@formkit/core'
import { createI18nPlugin, zh } from '@formkit/i18n'
import { genesisIcons } from '@formkit/icons'
import { createLibraryPlugin } from '@formkit/inputs'
import { createProPlugin, toggle } from '@formkit/pro'
import * as defaultRules from '@formkit/rules'
import { createThemePlugin } from '@formkit/themes'
import { createValidationPlugin } from '@formkit/validation'
import { /* defaultConfig, */ bindings, createInput } from '@formkit/vue'
import { addAsteriskPlugin } from './formkit.addAsteriskPlugin'
import { fkLibrary } from './formkit.config.fkLibrary'
import { rootClasses } from "./formkit.config.theme"
import HeadlessuiToggle from "./src/headlessui-switch.vue"
const validation = createValidationPlugin(defaultRules)
const i18n = createI18nPlugin({ zh })
const theme = undefined;
const icons = genesisIcons;
const themePlugin = createThemePlugin(theme, icons/* , iconLoaderUrl, iconLoader */)
const apiKey = 'fk-6cdd5192223'
export default {
plugins: [
createProPlugin(apiKey, { toggle }),
createLibraryPlugin(fkLibrary),
createLibraryPlugin(
{
'headlessuiSwitch': createInput(HeadlessuiToggle),
}
),
themePlugin, bindings, i18n, validation, addAsteriskPlugin,
// https://github.com/formkit/formkit/blob/ac1947a305eb5082ba95f53800305d080787cb32/packages/addons/src/plugins/autoHeightTextarea.ts
createAutoHeightTextareaPlugin(),
import { form } from '@/__fk-inputs__/inputs/form';
import { PCascadeSelect } from '@/__fk-inputs__/inputs/p-cascade-select';
import { PDatePicker } from '@/__fk-inputs__/inputs/p-date-picker';
import { PFileUpload } from '@/__fk-inputs__/inputs/p-file-upload';
import { PInputPassword } from '@/__fk-inputs__/inputs/p-input-password';
import { PInputText } from '@/__fk-inputs__/inputs/p-input-text';
import { PSelect } from '@/__fk-inputs__/inputs/p-select';
import { createAutoAnimatePlugin, createAutoHeightTextareaPlugin } from '@formkit/addons';
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
import type { FormKitOptions, FormKitPlugin } from '@formkit/core';
import { register as decodeErrors } from '@formkit/dev';
import { createI18nPlugin, zh } from '@formkit/i18n';
import { createLibraryPlugin, group, list, submit } from '@formkit/inputs';
import * as defaultRules from '@formkit/rules';
import { createValidationPlugin } from '@formkit/validation';
import { /* defaultConfig, */ bindings, plugin /* defaultConfig */ } from '@formkit/vue';
import type { App } from 'vue';
import { addAsteriskPlugin } from './formkit.config.plugin.addAsteriskPlugin';
import { debugPlugin } from './formkit.config.plugin.debug';
const plugins: FormKitPlugin[] = [
// createLibraryPlugin(fkLibrary),
createLibraryPlugin({
submit,
list,
group,
// ==============================
form,
PInputText,
PInputPassword,
PSelect,
PDatePicker,
PCascadeSelect,
PFileUpload,
}),
// createLibraryPlugin(
// {
// 'headlessuiSwitch': createInput(HeadlessuiToggle),
// }
// ),
// createThemePlugin(
// theme: undefined /* icons: genesisIcons, iconLoaderUrl, iconLoader */,
// ),
// #############################
bindings,
createI18nPlugin({ zh }),
createValidationPlugin(defaultRules),
addAsteriskPlugin,
createAutoHeightTextareaPlugin(/* https://github.com/formkit/formkit/blob/ac1947a305eb5082ba95f53800305d080787cb32/packages/addons/src/plugins/autoHeightTextarea.ts */),
createAutoAnimatePlugin(
// https://auto-animate.formkit.com/#usage
// https://github.com/formkit/auto-animate/
// https://github.com/formkit/formkit/blob/46d64d05c1b37875fc6227853f2bcfa987550c91/packages/addons/src/plugins/autoAnimatePlugin.ts
createAutoAnimatePlugin(
{
duration: 250,
easing: 'ease-in-out',
},
// {
// /* optional animation targets object */
// // default:
// global: ['outer', 'inner'],
// form: ['form'],
// repeater: ['items'],
// }
)
],
config: { rootClasses },
} satisfies FormKitOptions
{
/* optional animation targets object */
// default:
global: ['outer', 'inner'],
form: ['form'],
repeater: ['items'],
},
),
];
if (import.meta.env.VITE_DEBUG_FORMKIT === 'true') plugins.push(debugPlugin);
const config: FormKitOptions = {
plugins,
config: {},
};
export function setupFormKit(app: App) {
decodeErrors();
app.use(plugin, config);
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
}

View File

@ -1,6 +1,5 @@
<!doctype html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link
@ -10,21 +9,61 @@
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Vue FormKit Example</title>
<link
rel="stylesheet"
href="/src/assets/tailwind.css"
/>
<link
rel="stylesheet"
href="/src/assets/main.css"
>
/>
<script>
function setAppHeight() {
const app = document.getElementById('app');
app.style.minHeight = `${window.innerHeight}px`;
}
window.addEventListener('resize', setAppHeight);
window.addEventListener('load', setAppHeight);
</script>
<style>
#app {
min-height: 100vh;
max-width: 100vw;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
@supports (min-height: 100dvh) {
#app {
min-height: 100dvh;
max-width: 100dvw;
}
}
.page-wrapper {
flex-grow: 1;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="app">
<div
class="page-wrapper"
style="display: flex; justify-content: center; align-items: center"
>
Loading...
</div>
</div>
<script
type="module"
src="/src/main.ts"
></script>
</body>
</html>

View File

@ -12,27 +12,37 @@
},
"dependencies": {
"@formkit/addons": "^1.6.9",
"@formkit/core": "latest",
"@formkit/icons": "latest",
"@formkit/pro": "^0.127.7",
"@formkit/themes": "latest",
"@formkit/vue": "latest",
"@formkit/core": "^1.6.9",
"@formkit/icons": "^1.6.9",
"@formkit/pro": "^0.127.21",
"@formkit/themes": "^1.6.9",
"@formkit/vue": "1.6.10-fix.202501212134",
"@formkit/zod": "^1.6.9",
"@headlessui/vue": "^1.7.23",
"autoprefixer": "latest",
"i18next": "^23.16.6",
"postcss": "latest",
"sweetalert2": "^11.14.5",
"tailwindcss": "latest",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"autoprefixer": "^10.4.20",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"i18next": "^24.2.2",
"postcss": "^8.5.2",
"primeicons": "^7.0.0",
"primelocale": "^1.6.0",
"primevue": "4.2.5",
"sweetalert2": "^11.17.2",
"tailwindcss": "^3.4.17",
"utils4u": "^4.0.0",
"vue": "^3.5.13",
"zod-i18n-map": "^2.27.0"
},
"devDependencies": {
"@unocss/extractor-arbitrary-variants": "^0.64.1",
"@vitejs/plugin-vue": "^5.2.0",
"typescript": "~5.6.3",
"unocss": "^0.64.1",
"vite": "^5.4.11",
"vue-tsc": "^2.1.10"
"@primevue/auto-import-resolver": "^4.2.5",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"typescript": "~5.7.3",
"unocss": "^65.5.0",
"unplugin-vue-components": "^28.2.0",
"vite": "^6.1.0",
"vue-tsc": "^2.2.2"
}
}

2347
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,110 @@
<script setup lang="ts">
import TutorialForm from './tutorial-form/index.vue'
import ZodForm from './zod-form/index.vue'
import { ref } from 'vue';
import AllCustom from './all-custom/all-custom.vue';
import TutorialForm from './tutorial-form/index.vue';
import ZodForm from './zod-form/index.vue';
const selectedCity = ref();
const countries = ref([
{
name: 'Australia',
code: 'AU',
states: [
{
name: 'New South Wales',
cities: [
{ cname: 'Sydney', code: 'A-SY' },
{ cname: 'Newcastle', code: 'A-NE' },
{ cname: 'Wollongong', code: 'A-WO' }
]
},
{
name: 'Queensland',
cities: [
{ cname: 'Brisbane', code: 'A-BR' },
{ cname: 'Townsville', code: 'A-TO' }
]
}
]
},
{
name: 'Canada',
code: 'CA',
states: [
{
name: 'Quebec',
cities: [
{ cname: 'Montreal', code: 'C-MO' },
{ cname: 'Quebec City', code: 'C-QU' }
]
},
{
name: 'Ontario',
cities: [
{ cname: 'Ottawa', code: 'C-OT' },
{ cname: 'Toronto', code: 'C-TO' }
]
}
]
},
{
name: 'United States',
code: 'US',
states: [
{
name: 'California',
cities: [
{ cname: 'Los Angeles', code: 'US-LA' },
{ cname: 'San Diego', code: 'US-SD' },
{ cname: 'San Francisco', code: 'US-SF' }
]
},
{
name: 'Florida',
cities: [
{ cname: 'Jacksonville', code: 'US-JA' },
{ cname: 'Miami', code: 'US-MI' },
{ cname: 'Tampa', code: 'US-TA' },
{ cname: 'Orlando', code: 'US-OR' }
]
},
{
name: 'Texas',
cities: [
{ cname: 'Austin', code: 'US-AU' },
{ cname: 'Dallas', code: 'US-DA' },
{ cname: 'Houston', code: 'US-HO' }
]
}
]
}
]);
</script>
<template>
<ZodForm />
<TutorialForm />
<div class="page-wrapper p-4">
<ZodForm v-if="false" />
<TutorialForm v-if="false" />
<!-- <div class="flex flex-col md:flex-row items-start p-4 gap-4"> -->
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white">
<AllCustom />
</div>
<!-- </div> -->
<div class="p-4 w-full bg-white rounded-lg shadow-md dark:bg-gray-800 dark:text-white mt-4">
<CascadeSelect
loading
v-model="selectedCity"
:options="countries"
optionLabel="cname"
optionGroupLabel="name"
:optionGroupChildren="['states', 'cities']"
class="w-56"
placeholder="Select a City"
/>
</div>
</div>
</template>

View File

@ -0,0 +1,77 @@
<script setup lang="ts">
import TimesIcon from '@primevue/icons/times';
import type { FileUploadStatus } from './types';
import { computed } from 'vue';
import type { BadgeProps } from 'primevue/badge';
const props = defineProps<{
disabled: boolean;
url: string;
filename: string;
status?: FileUploadStatus;
progress?: number;
}>();
defineEmits<{
remove: () => void;
}>();
const bageValue = computed(() => {
switch (props.status) {
case 'pending':
return '待上传';
case 'uploading':
return '上传中';
case 'failed':
return '上传失败';
case 'uploaded':
return '已上传';
default:
return '未知状态';
}
});
const bageSeverity = computed<BadgeProps['severity']>(() => {
switch (props.status) {
case 'pending':
return 'info';
case 'uploading':
return 'warn';
case 'failed':
return 'danger';
case 'uploaded':
return 'success';
default:
return 'contrast';
}
});
</script>
<template>
<div class="border border-[var(--p-fileupload-border-color)] p-2 rounded-md flex flex-wrap items-center gap-2">
<slot name="image">
<img
:src="url"
class="w-10 h-10 shrink-0 of-hidden"
/>
</slot>
<div class="break-all break-anywhere">{{ filename }}</div>
<Badge
:value="bageValue"
:severity="bageSeverity"
/>
<ProgressBar
v-if="status === 'uploading'"
:value="progress"
/>
<Button
:disabled="disabled"
text
:rounded="true"
severity="danger"
class="ml-auto"
@click="$emit('remove')"
>
<TimesIcon aria-hidden="true" />
</Button>
</div>
</template>

View File

@ -0,0 +1,237 @@
<script setup lang="ts">
import type { FormKitFrameworkContext } from '@formkit/core';
import FileUpload, { type FileUploadUploaderEvent } from 'primevue/fileupload';
import { computed, onMounted, useTemplateRef } from 'vue';
import FileUploadItem from './file-upload-item.vue';
import type { CustomRequest, FileExt, FileUploadInst, PropFilesToValue, PropValueToFiles } from './types';
const props = defineProps<{
context: FormKitFrameworkContext & {
fileLimit?: number;
maxFileSize?: number;
customRequest?: CustomRequest;
valueToFiles?: PropValueToFiles;
filesToValue?: PropFilesToValue;
autoUpload?: boolean;
};
}>();
const formkitContext = props.context;
const customRequest = props.context.customRequest;
const fileUploadRef = useTemplateRef<FileUploadInst>('fileUploadRef');
const cmpt_disabled = computed(() => {
if (formkitContext.disabled === true) {
return true;
}
if (fileUploadRef.value) {
// 有上传失败的文件
if (fileUploadRef.value.uploadedFiles.some((f) => f.status === 'failed')) {
return true;
}
// 已上传文件数量超过限制(这里是大于*等于*
if (fileUploadRef.value.uploadedFileCount >= (formkitContext.fileLimit ?? Infinity)) {
return true;
}
// 已上传和待上传文件数量超过限制(这里是*大于*
const uploaded_and_pending_count = fileUploadRef.value.uploadedFileCount + fileUploadRef.value.files.length;
if (uploaded_and_pending_count > (formkitContext.fileLimit ?? Infinity)) {
return true;
}
}
return false;
});
const cmpt_showUploadButton = computed(() => {
if (fileUploadRef.value?.files?.length) {
return true;
}
return false;
});
const changeModelValue = () => {
const uploadedFiles = fileUploadRef.value!.uploadedFiles.filter((f) => f.status === 'uploaded');
if (!formkitContext.filesToValue) {
console.warn('[FileUpload] filesToValue is not defined');
return;
}
if (uploadedFiles.length === 0) {
formkitContext.node.input(null);
} else {
formkitContext.node.input(formkitContext.filesToValue(uploadedFiles));
}
};
const changeUploadedFiles = () => {
if (!formkitContext.valueToFiles) {
console.warn('[FileUpload] valueToFiles is not defined');
return;
}
try {
const files = formkitContext.valueToFiles(formkitContext._value);
if (files === null) return;
fileUploadRef.value!.uploadedFiles = files.map((f) => ({
name: f.name,
url: f.url,
status: f.status || 'uploaded',
progress: f.progress || 100,
}));
fileUploadRef.value!.uploadedFileCount = files.length;
} catch (error) {
console.warn('[FileUpload] valueToFiles error:', error, 'value:', formkitContext._value);
}
};
onMounted(() => {
changeUploadedFiles();
});
const onUploader = (event: FileUploadUploaderEvent) => {
if (!customRequest) {
console.warn('[FileUpload] customRequest is not defined');
return;
}
const files = event.files as FileExt[];
for (const file of files) {
fileUploadRef.value!.uploadedFiles.push({
rawFile: file,
name: file.name,
url: '',
status: 'uploading',
progress: 0,
});
const fileItem = fileUploadRef.value!.uploadedFiles[fileUploadRef.value!.uploadedFiles.length - 1];
customRequest({
file,
onProgress: (percent) => {
fileItem.progress = percent;
},
})
.then((result) => {
fileItem.status = 'uploaded';
fileItem.url = result.url;
changeModelValue();
})
.catch(() => {
fileItem.status = 'failed';
});
}
};
// fileUploadRef.value?.uploadedFiles
</script>
<template>
<FileUpload
ref="fileUploadRef"
:auto="formkitContext.autoUpload"
:disabled="cmpt_disabled"
:showUploadButton="cmpt_showUploadButton"
:showCancelButton="false"
:customUpload="true"
mode="advanced"
:multiple="true"
accept="image/*"
:maxFileSize="formkitContext.maxFileSize"
invalidFileSizeMessage="文件 {0} 大小超过限制 {1}"
:fileLimit="formkitContext.fileLimit"
invalidFileLimitMessage="最多只能上传 {0} 个文件,请移除多余文件后点击上传"
@uploader="onUploader"
:chooseButtonProps="{ size: 'small' }"
:uploadButtonProps="{ size: 'small', severity: 'secondary' }"
:cancelButtonProps="{ size: 'small', severity: 'secondary' }"
@remove="changeModelValue()"
@removeUploadedFile="changeModelValue()"
>
<template #empty>
<Message
size="small"
severity="secondary"
>未上传附件</Message
>
</template>
<template #content="{ messages, removeFileCallback, removeUploadedFileCallback }">
<Message
size="small"
v-for="msg of messages"
closable
@close="fileUploadRef!.messages = []"
:key="msg"
severity="error"
>{{ msg }}</Message
>
<!-- 已上传列表上传中上传成功上传失败 -->
<template
v-for="(file, index) of fileUploadRef?.uploadedFiles"
:key="file.name + file.url"
>
<FileUploadItem
:disabled="formkitContext.disabled === true"
:url="file.url || file.rawFile?.objectURL || ''"
:filename="file.name || file.url || '未知文件'"
@remove="
() => {
fileUploadRef!.uploadedFileCount--;
removeUploadedFileCallback(index);
}
"
:status="file.status"
:progress="file.progress"
>
<template
#image
v-if="formkitContext.slots.image"
>
<component
:is="formkitContext.slots.image"
:url="file.url || file.rawFile?.objectURL || ''"
/>
</template>
</FileUploadItem>
</template>
<!-- 待上传列表 -->
<template
v-for="(file, index) of fileUploadRef?.files"
:key="file.name + file.type + file.size"
>
<FileUploadItem
:disabled="formkitContext.disabled === true"
:url="file.objectURL"
:filename="file.name"
@remove="removeFileCallback(index)"
status="pending"
>
<template
#image
v-if="formkitContext.slots.image"
>
<component
:is="formkitContext.slots.image"
:url="file.objectURL"
/>
</template>
</FileUploadItem>
</template>
</template>
</FileUpload>
</template>
<style>
.p-floatlabel:has(.p-fileupload) label {
top: var(--p-floatlabel-over-active-top);
transform: translateY(0);
font-size: var(--p-floatlabel-active-font-size);
font-weight: var(--p-floatlabel-label-active-font-weight);
}
.p-fileupload-content .p-progressbar {
--p-fileupload-progressbar-height: 1rem;
}
</style>

View File

@ -0,0 +1,28 @@
import type { FileUploadState } from 'primevue/fileupload';
export interface FileExt extends File {
objectURL: string;
}
export type FileUploadStatus = 'pending' | 'uploading' | 'uploaded' | 'failed';
export type UploadedFileInfo = {
rawFile?: FileExt;
name?: string;
url?: string;
status?: FileUploadStatus;
progress?: number;
};
export interface FileUploadInst extends FileUploadState {
files: FileExt[];
uploadedFiles: UploadedFileInfo[];
chooseDisabled?: boolean;
}
export type CustomRequest = (options: {
file: File;
onProgress: (percent: number) => void;
}) => Promise<{ url: string }>;
export type PropValueToFiles = (value: unknown) => UploadedFileInfo[] | null;
export type PropFilesToValue = (filelist: UploadedFileInfo[]) => unknown;

View File

@ -0,0 +1,31 @@
<template>
<Message
severity="error"
size="small"
>
<!-- <template #container> -->
<ul class="my-0 flex flex-col gap-1">
<li
v-for="message in context.messages"
:key="message.key"
:id="`${context.id}-${message.key}`"
:data-message-type="message.type"
>
{{ message.value }}
</li>
</ul>
<!-- </template> -->
</Message>
</template>
<script setup lang="ts">
import { type FormKitFrameworkContext } from '@formkit/core';
import Message from 'primevue/message';
defineProps<{ context: FormKitFrameworkContext }>();
</script>
<style>
.p-floatlabel + .p-message {
margin-top: 0.25rem;
}
</style>

View File

@ -0,0 +1,50 @@
import type { FormKitTypeDefinition } from '@formkit/core';
import { createSection, disablesChildren, forms } from '@formkit/inputs';
import PButton from 'primevue/button';
import { markRaw } from 'vue';
import { messages } from '../sections/messages';
const formInput = createSection('form', () => ({
$el: 'form',
bind: '$attrs',
meta: {
autoAnimate: true,
},
attrs: {
'id': '$id',
'name': '$node.name',
'onSubmit': '$handlers.submit',
'data-loading': '$state.loading || undefined',
},
}));
const button = createSection(
// https://formkit.com/playground?fkv=latest&fileTab=Playground.vue&files=jc%5B%28%27name%21%27Playground.vue%27%7Eeditor%21%27%3Cscripzsetup%3EBtestUgWasyncJEqawaiznew+Promise%7Br3%3E+setTimeout%7Br%2C+5000%7D%7D-%29-BsGW%5Bq%286%24formkit142*lX1LogU2%29-%5D%3B-B_W%28qY1Uput2sG8%5B6%286*%24cmp19xbUdZsHAttrsxprops8%286Nignore8true%2C6NY1sHx*OZOx*lXZsHLXx%29%2C6*V6N%22%24lXx*%286N*ifZOxN%24el1spanxNV%22+loadUg...%22%5D6N%29%2C6*%5D6%29q%5D%2C-%29-BC3JE%29--%2F%2F+Then+we+attach+a+library-C.library3JnodeEqifJnode.props.Y3%3D%3D+%224%22%7D+%286node.defUe%7B_%7Dq%29-%29--MscriptQ-%3Cj-+%3C96YRform76%3AplugUsR%5BC%5D76%3AactionsRfalse76%40sHRtestUg7q%3E6%3C9SG+%3AsGRsG7+%2F%3E6M9Q*M9QMj%27%7Eadded%21true%29%5D*++-%5Cn18%222%22%2CqW%3D4spUnUgSH6-N7%5C%278%3A+9FormKitB-conszCcustomInputsE%7D3%3E+%28GchemaHubmitJ+%7BM%3C%2FN**OdisabledQ%3E-R%3D7UinVchildren8%5BW3+XabelYtypeZ1%24_4DefUitionjtemplateQq-*x2Nzt+%01zxqj_ZYXWVURQONMJHGECB98764321-*_&imports=jc%28%27name%21%27ImportMap%27%7Eeditor%21%27%28*+1vue%5C%211https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fvue%403%2Fdist%2Fvue.esm-browser.min.js0*%29*%27%29*%5Cn0%5C%271+0%0110*_&css-framework=genesis
'button',
() => ({
$cmp: markRaw(PButton) as never,
bind: '$submitAttrs',
props: {
type: 'submit',
label: '$submitLabel',
loading: '$state.loading || undefined',
},
}),
);
export const form: FormKitTypeDefinition = {
type: 'group',
schema: formInput(
'$slots.default',
messages(),
button(), //
),
props: [
'submitLabel',
'submitAttrs',
'submitBehavior',
'incompleteMessage', // 抱歉,部分字段未被正确填写。
],
features: [forms, disablesChildren],
schemaMemoKey: 'i90xrn9dyre', // Math.random().toString(36).substring(2, 15)
};

View File

@ -0,0 +1,115 @@
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { createSection, label, outer } from '@formkit/inputs';
import type { CascadeSelectEmitsOptions, CascadeSelectProps } from 'primevue/cascadeselect';
import PrimevueCascadeselect from 'primevue/cascadeselect';
import { defineComponent, markRaw, ref } from 'vue';
import { floatLabel } from '../sections/floatLabel';
import { messages } from '../sections/messages';
import { help } from '../sections/help';
// https://formkit.com/inputs/dropdown
type PrimevueSelectListeners = {
'onUpdate:modelValue': CascadeSelectEmitsOptions['update:modelValue'];
'onBlur': CascadeSelectEmitsOptions['blur'];
};
const SchemaComponent = defineComponent(
(vueProps: { context: FormKitFrameworkContext }) => {
const formkitContext = vueProps.context;
const listeners: PrimevueSelectListeners = {
'onUpdate:modelValue': (value: unknown) => {
formkitContext.node.input(value);
},
'onBlur': async (e) => {
setTimeout(
() => formkitContext.handlers.blur.call(undefined, e as never),
166, // 因为会触发两次。所以让blur事件延迟一点可以考虑优化。Cascadeselect好像没有这个问题
);
},
};
const p_options = ref();
const loading = ref(true);
const loadOptions = async () => {
try {
let result;
if (formkitContext.options instanceof Promise) {
result = await formkitContext.options;
} else {
console.warn('未支持的 options 类型 :>> ', typeof formkitContext.options);
}
p_options.value = result;
} catch (error) {
console.error('Failed to load options:', error);
p_options.value = [];
} finally {
loading.value = false;
}
};
loadOptions(); // 立即加载options
return () => {
return (
<PrimevueCascadeselect
inputId={formkitContext.id}
optionLabel={formkitContext.optionLabel as never}
optionValue={formkitContext.optionValue as never}
optionGroupChildren={(formkitContext.optionGroupChildren as never) || 'children'}
optionGroupLabel={(formkitContext.optionGroupLabel as never) || (formkitContext.optionLabel as never)}
loading={loading.value}
options={p_options.value}
invalid={formkitContext.state.invalid}
fluid
disabled={!!formkitContext.disabled}
modelValue={formkitContext._value}
{...listeners}
/>
);
};
},
// https://cn.vuejs.org/api/general#definecomponent
// 目前仍然需要手动声明运行时的 props
// 在将来,我们计划提供一个 Babel 插件,自动推断并注入运行时 props (就像在单文件组件中的 defineProps 一样),以便省略运行时 props 的声明。
{
props: ['context'],
},
);
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
context: '$node.context',
},
}));
export const PCascadeSelect: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
props: ['options', 'optionLabel', 'optionValue'],
schemaMemoKey: '72psvunq45', // Math.random().toString(36).substring(2, 15)
};
type OptionsType = CascadeSelectProps['options'];
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PCascadeSelect: {
type: 'PCascadeSelect';
options: Promise<OptionsType>;
optionLabel: string;
optionValue: string;
};
}
}

View File

@ -0,0 +1,107 @@
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { createSection, label, outer } from '@formkit/inputs';
import type { DatePickerEmitsOptions, DatePickerProps } from 'primevue/datepicker';
import PrimevueDatepicker from 'primevue/datepicker';
import { defineComponent, markRaw } from 'vue';
import { floatLabel } from '../sections/floatLabel';
import { messages } from '../sections/messages';
import { help } from '../sections/help';
// :plugins="[castNumber]"
type PickerValue = Date | Array<Date> | Array<Date | null> | undefined | null;
type PropValueToDate = (value: unknown) => PickerValue;
type PropDateToValue = (date?: PickerValue) => unknown;
type PrimevueDatepickerListeners = {
'onUpdate:modelValue': DatePickerEmitsOptions['update:modelValue'];
'onBlur': DatePickerEmitsOptions['blur'];
};
const SchemaComponent = defineComponent(
(vueProps: { context: FormKitFrameworkContext & { valueToDate: PropValueToDate; dateToValue: PropDateToValue } }) => {
const formkitContext = vueProps.context;
const { valueToDate, dateToValue } = formkitContext;
const listeners: PrimevueDatepickerListeners = {
'onUpdate:modelValue': (value) => {
let newValue = value as unknown;
try {
newValue = dateToValue(value);
} catch (e) {
console.error(e);
}
formkitContext.node.input(newValue);
},
'onBlur': async (e) => {
setTimeout(
() => formkitContext.handlers.blur.call(undefined, e as never),
166, // 因为会触发两次。所以让blur事件延迟一点可以考虑优化。
);
},
};
return () => {
let value = formkitContext._value;
try {
value = valueToDate(formkitContext._value);
} catch (e) {
console.error(e);
}
return (
<PrimevueDatepicker
inputId={formkitContext.id}
fluid
invalid={formkitContext.state.invalid}
disabled={!!formkitContext.disabled}
modelValue={value}
dateFormat={formkitContext.dateFormat as never}
manualInput={formkitContext.manualInput as never}
{...listeners}
/>
);
};
},
{
props: ['context'],
},
);
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
context: '$node.context',
},
}));
export const PDatePicker: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
props: ['valueToDate', 'dateToValue', 'dateFormat'],
schemaMemoKey: 'q2dkascustq', // Math.random().toString(36).substring(2, 15)
};
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PDatePicker: {
type: 'PDatePicker';
valueToDate?: PropValueToDate;
dateToValue?: PropDateToValue;
value?: unknown;
view?: DatePickerProps['view'];
/**
* https://primevue.org/datepicker/#format
*/
dateFormat?: DatePickerProps['dateFormat'];
manualInput?: DatePickerProps['manualInput'];
};
}
}

View File

@ -0,0 +1,58 @@
import type { FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs, FormKitSlotData } from '@formkit/inputs';
import { createSection, label, outer } from '@formkit/inputs';
import { markRaw } from 'vue';
import FileUploadComponent from '../components/file-upload/file-upload.vue';
import type { CustomRequest, PropFilesToValue, PropValueToFiles } from '../components/file-upload/types';
import { floatLabel } from '../sections/floatLabel';
import { help } from '../sections/help';
import { messages } from '../sections/messages';
const input = createSection('input', () => ({
$cmp: markRaw(FileUploadComponent) as never,
bind: '$attrs',
props: {
context: '$node.context',
},
}));
export const PFileUpload: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
props: ['fileLimit', 'maxFileSize', 'customRequest', 'valueToFiles', 'filesToValue', 'autoUpload'],
schemaMemoKey: 'ihcxd4qdgh7', // Math.random().toString(36).substring(2, 15)
};
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PFileUpload: {
type: 'PFileUpload';
value?: unknown;
fileLimit?: number;
maxFileSize?: number;
customRequest?: CustomRequest;
valueToFiles?: PropValueToFiles;
filesToValue?: PropFilesToValue;
autoUpload?: boolean;
};
}
interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
PFileUpload: {
image: FormKitSlotData<
Props,
{
url: string;
}
>;
};
}
}

View File

@ -0,0 +1,45 @@
import type { FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { casts, createSection, label, outer } from '@formkit/inputs';
import SchemaComponent from 'primevue/password';
import { markRaw } from 'vue';
import { messages } from '../sections/messages';
import { floatLabel } from '../sections/floatLabel';
import { help } from '../sections/help';
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
invalid: '$state.invalid',
disabled: '$disabled',
modelValue: '$_value',
onInput: '$handlers.DOMInput',
onBlur: '$handlers.blur',
inputId: '$id',
fluid: true,
},
}));
export const PInputPassword: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
features: [casts],
schemaMemoKey: '9h771fci93n', // Math.random().toString(36).substring(2, 15)
};
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PInputPassword: {
type: 'PInputPassword';
};
}
}

View File

@ -0,0 +1,47 @@
import type { FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { casts, createSection, label, outer } from '@formkit/inputs';
import SchemaComponent from 'primevue/inputtext';
import { markRaw } from 'vue';
import { floatLabel } from '../sections/floatLabel';
import { messages } from '../sections/messages';
import { help } from '../sections/help';
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
invalid: '$state.invalid',
type: '$type',
disabled: '$disabled',
name: '$node.name',
modelValue: '$_value',
onInput: '$handlers.DOMInput',
onBlur: '$handlers.blur',
id: '$id',
fluid: true,
},
}));
export const PInputText: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
features: [casts],
schemaMemoKey: 'nnvujvlf2xr', // Math.random().toString(36).substring(2, 15)
};
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PInputText: {
type: 'PInputText';
};
}
}

View File

@ -0,0 +1,139 @@
import type { FormKitFrameworkContext, FormKitTypeDefinition } from '@formkit/core';
import type { FormKitInputs } from '@formkit/inputs';
import { createSection, label, outer } from '@formkit/inputs';
import PrimevueSelect, { type SelectEmitsOptions, type SelectSlots } from 'primevue/select';
import { computed, defineComponent, markRaw, ref } from 'vue';
import { floatLabel } from '../sections/floatLabel';
import { messages } from '../sections/messages';
import { help } from '../sections/help';
// https://formkit.com/inputs/dropdown
type PrimevueSelectListeners = {
// 'onFocus': SelectEmitsOptions['focus'];
'onUpdate:modelValue': SelectEmitsOptions['update:modelValue'];
// 'onChange': SelectEmitsOptions['change'];
'onBlur': SelectEmitsOptions['blur'];
};
const SchemaComponent = defineComponent(
(vueProps: { context: FormKitFrameworkContext }) => {
const formkitContext = vueProps.context;
const primevueSelectInstance = ref<{ label: string } | undefined>();
const listeners: PrimevueSelectListeners = {
'onUpdate:modelValue': (value: unknown) => {
formkitContext.node.input(value);
},
'onBlur': async (e) => {
setTimeout(
() => formkitContext.handlers.blur.call(undefined, e as never),
166, // 因为会触发两次。所以让blur事件延迟一点可以考虑优化。
);
},
};
// [optionsLoader](https://github.com/formkit/formkit/blob/2d5387ba98597775cb2a752af65aee84bc438863/packages/inputs/src/features/options.ts#L125)
const p_options = ref();
const loading = ref(true);
const loadOptions = async () => {
try {
let result;
if (formkitContext.options instanceof Promise) {
result = await formkitContext.options;
} else if (typeof formkitContext.options === 'function') {
const funcResult = await (formkitContext.options as () => unknown).call(undefined);
result = funcResult instanceof Promise ? await funcResult : funcResult;
} else {
result = formkitContext.options;
}
p_options.value = result;
} catch (error) {
console.error('Failed to load options:', error);
p_options.value = [];
} finally {
loading.value = false;
}
};
loadOptions(); // 立即加载options
const valueSlot = computed(() => {
if (primevueSelectInstance.value?.label === 'p-emptylabel' && !!formkitContext._value) {
return ((slotProps) => {
return slotProps.value as never;
}) satisfies SelectSlots['value'];
} else {
return undefined;
}
});
return () => {
return (
<PrimevueSelect
style={{
'border-color':
!formkitContext.state.invalid && valueSlot.value !== undefined ? 'var(--p-yellow-200)' : undefined,
}}
ref={primevueSelectInstance}
labelId={formkitContext.id}
fluid
invalid={formkitContext.state.invalid}
disabled={!!formkitContext.disabled}
loading={loading.value}
options={p_options.value}
modelValue={formkitContext._value}
optionLabel={formkitContext.optionLabel as never}
optionValue={formkitContext.optionValue as never}
{...listeners}
>
{{
value: valueSlot.value,
}}
</PrimevueSelect>
);
};
},
// https://cn.vuejs.org/api/general#definecomponent
// 目前仍然需要手动声明运行时的 props
// 在将来,我们计划提供一个 Babel 插件,自动推断并注入运行时 props (就像在单文件组件中的 defineProps 一样),以便省略运行时 props 的声明。
{
props: ['context'],
},
);
const input = createSection('input', () => ({
$cmp: markRaw(SchemaComponent) as never,
bind: '$attrs',
props: {
context: '$node.context',
},
}));
export const PSelect: FormKitTypeDefinition = {
type: 'input',
schema: outer(
floatLabel(
input(), //
label('$label'),
),
help('$help'),
messages(),
),
props: ['options', 'optionLabel', 'optionValue'],
schemaMemoKey: 'nip9j70lb1', // Math.random().toString(36).substring(2, 15)
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type OptionsItem = Record<string, any>;
type OptionsType = Array<OptionsItem>;
declare module '@formkit/inputs' {
// https://formkit.com/essentials/custom-inputs#typescript-support
interface FormKitInputProps<Props extends FormKitInputs<Props>> {
PSelect: {
type: 'PSelect';
options: OptionsType | Promise<OptionsType> | (() => OptionsType | Promise<OptionsType>);
};
}
}

View File

@ -0,0 +1,8 @@
import { createSection } from '@formkit/inputs';
import FloatLabel from 'primevue/floatlabel';
import { markRaw } from 'vue';
export const floatLabel = createSection('floatLabel', () => ({
$cmp: markRaw(FloatLabel) as never,
bind: '$attrs',
}));

View File

@ -0,0 +1,14 @@
import { createSection } from '@formkit/inputs';
/**
* Help section that shows help text
*
* @public
*/
export const help = createSection('help', () => ({
$el: 'div',
if: '$help',
attrs: {
id: '$: "help-" + $id',
},
}));

View File

@ -0,0 +1,9 @@
import MessagesCmp from '../components/messages.vue';
import { createSection } from '@formkit/inputs';
import { markRaw } from 'vue';
export const messages = createSection('messages', () => ({
$cmp: markRaw(MessagesCmp) as unknown as never, // [element = node.$cmp](https://github.com/formkit/formkit/blob/2d5387ba98597775cb2a752af65aee84bc438863/packages/vue/src/FormKitSchema.ts#L449)
props: { context: '$node.context' },
if: '$defaultMessagePlacement && $fns.length($messages)',
}));

View File

@ -0,0 +1,253 @@
<script setup lang="ts">
import { FormKitNode } from '@formkit/core';
import { text } from '@formkit/inputs';
import Swal from 'sweetalert2';
import dayjs from 'dayjs';
import { arrayToTree } from 'utils4u/array';
import { CustomRequest } from '@/__fk-inputs__/components/file-upload/types';
async function submit(formData: Record<string, any>, formNode: FormKitNode) {
console.group('submit');
console.log('formData :>> ', formData);
console.log('formNode :>> ', formNode);
console.groupEnd();
await new Promise(r => setTimeout(r, 2000))
Swal.fire({
title: 'Submitted! 🎉',
icon: 'success',
showConfirmButton: false,
timer: 1500
})
}
const K_OPTIONS = [
{ label: 'Option 1', value: 'Value 1' },
{ label: 'Option 2', value: 'Value 2' },
{ label: 'Option 3', value: 'Value 3' },
];
const promiseOptions = new Promise<typeof K_OPTIONS>(resolve => {
setTimeout(() => resolve(K_OPTIONS), 1000);
});
const K_FLAT_TREE = [
{
dictLabel: '北京市',
dictValue: '110000',
fullName: '北京市',
abbrName: '北京',
dictParent: '00',
},
{
dictLabel: '山西省',
dictValue: '140000',
fullName: '山西省',
abbrName: '山西',
dictParent: '00',
},
{
dictLabel: '太原市',
dictValue: '140100',
fullName: '山西省,太原市',
abbrName: '山西太原',
dictParent: '140000',
},
{
dictLabel: '大同市',
dictValue: '140200',
fullName: '山西省,大同市',
abbrName: '山西大同',
dictParent: '140000',
},
]
const promiseCascadeOptions = new Promise<typeof K_FLAT_TREE>((resolve) => {
setTimeout(() => {
resolve(arrayToTree(K_FLAT_TREE, { id: 'dictValue', parentId: 'dictParent', rootId: '00' }));
}, 1000);
});
/* const funcOptions = async () => {
await new Promise(r => setTimeout(r, 1000))
return K_OPTIONS;
} */
import axios from 'axios';
const customRequest: CustomRequest = (async ({ file, onProgress, }) => {
const formData = new FormData();
formData.append('file', file);
return axios
.post('https://jsonplaceholder.typicode.com/posts', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (e) => {
onProgress(Math.round((e.loaded * 100) / (e.total || 1)));
},
})
.then(() => {
// onProgress(100);
return { url: 'https://picsum.photos/200/300' };
});
});
</script>
<template>
<Fieldset
legend="表单"
toggleable
class="min-w-full"
pt:content:class="flex justify-center"
>
<!-- https://formkit.com/inputs/form#props-attributes -->
<FormKit
:value="{
a: '1', b: '2',
PInputText: 'PInputText default value from form',
notInForm: 'not in form',
}"
:config="{
classes: {
form: 'flex flex-col w-full py-6 gap-8',
outer: 'flex flex-col gap-2',
/* label: 'font-medium block' */
},
}"
type="form"
#default="{ value }"
@submit="submit"
submit-label="提交 "
:submit-attrs="{
'some-submit-attr': 'value',
}"
>
<FormKit
type="PInputText"
name="PInputText"
label="输入框"
value="`default value from field`"
help="帮助信息帮助信息帮助信息帮助信息"
some="prop"
some-boolean-prop
validation="required"
>
</FormKit>
<FormKit
name="PFileUpload"
type="PFileUpload"
label="文件上传"
:maxFileSize="1024 * 1024 * 2"
:customRequest
:fileLimit="99"
:autoUpload="true"
:filesToValue="(files) => JSON.stringify(files.map(f => ({ name: f.name, url: f.url })))"
:valueToFiles="(value) => JSON.parse(value as string)"
value='[{"name":"2KB图片_副本.jpeg","url":"https://picsum.photos/200/300"}]'
/>
<FormKit
type="PInputPassword"
name="PInputPassword"
label="密码"
validation="required"
toggleMask
:feedback="false"
/>
<FormKit
type="PSelect"
name="PSelect_1"
label="选择框"
validation="required"
:options="K_OPTIONS"
optionLabel="label"
optionValue="value"
value="数据的值不在options里"
/>
<FormKit
type="PSelect"
name="PSelect_2"
label="选择框"
validation="required"
:options="promiseOptions"
optionLabel="label"
optionValue="value"
/>
<FormKit
type="PCascadeSelect"
name="PCascadeSelect"
label="级联选择框"
validation="required"
:options="promiseCascadeOptions"
optionLabel="dictLabel"
optionValue="dictValue"
/>
<FormKit
type="PDatePicker"
name="PDatePicker"
:manualInput="false"
dateFormat="yy年mm月"
:dateToValue="(date) => dayjs(date as Date).format('YYYY-MM')"
:valueToDate="(value) => dayjs(value as string).toDate()"
view="month"
value="2022-01"
label="选日期"
validation="required"
/>
<div class="flex flex-wrap gap-4">
<FormKit
v-if="!value?.PInputPassword"
:type="text"
:preserve="false"
name="customType"
value="this input will display if the password is empty"
help="helppppppppppp"
>
<!-- <template #label>
<div>labelll</div>
</template> -->
</FormKit>
<FormKit
type="group"
name="group"
>
<FormKit
:type="text"
name="text-in-group-1"
>
</FormKit>
</FormKit>
<FormKit
type="group"
name="group"
>
<FormKit
:type="text"
name="text-in-group-2"
>
</FormKit>
</FormKit>
</div>
<Button
label="Button"
@click="() => {
value!.button = value!.button || {};
(value!.button as any).clicked = 'clicked-这个值在@submit回调的formData里不会出现';
(value!.group as any)['text-in-group-1'] = 'changed-by-button';
}"
/>
<pre class="font-mono text-sm p-4 bg-slate-100 dark:bg-gray-900 overflow-x-auto rounded-md shadow-inner dark:text-gray-300
">{{ value }}</pre>
<!-- <FormKitSummary /> -->
</FormKit>
</Fieldset>
</template>
<style scoped>
:deep(.formkit-input) {
border: 1px solid #e2e8f0;
}
</style>

View File

@ -1,7 +1,3 @@
/* @tailwind base; */
/* @tailwind components; */
/* @tailwind utilities; */
body {
background-color: #f3f4f6;
color: #1f2937;
@ -14,7 +10,6 @@ body {
}
}
.attributes-group {
padding: 1.5rem;
border: 1px solid #e5e7eb;
@ -29,3 +24,9 @@ body {
background-color: #1f2937;
}
}
/* DEV: */
/* .formkit-outer {
border: 1px solid #e5e7eb;
} */

3
src/assets/tailwind.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@ -1,17 +1,40 @@
import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
import { plugin, /* defaultConfig */ } from '@formkit/vue'
import { createApp } from 'vue'
import formKitConfig from '../formkit.config'
import App from './App.vue'
import Aura from '@primevue/themes/aura';
import zhCN from 'primelocale/zh-CN.json';
import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
import { createApp } from 'vue';
import { setupFormKit } from '../formkit.config';
import App from './App.vue';
// import '@unocss/reset/normalize.css'
// import '@unocss/reset/sanitize/sanitize.css'
// import '@unocss/reset/sanitize/assets.css'
// import '@unocss/reset/eric-meyer.css'
// import '@unocss/reset/tailwind-compat.css'
import '@unocss/reset/tailwind.css'
import 'virtual:uno.css'
// import '@unocss/reset/tailwind.css'
const app = createApp(App)
import 'primeicons/primeicons.css';
import 'virtual:uno.css';
const app = createApp(App);
app.use(PrimeVue, {
locale: {
...zhCN['zh-CN'],
noFileChosenMessage: '未选择文件',
pending: '待上传',
completed: '已上传',
}, // usePrimeVue().config.locale
theme: { preset: Aura },
options: {
prefix: 'p',
darkModeSelector: 'system',
cssLayer: false,
},
});
app.use(ToastService);
// https://github.dev/formkit/auto-animate/blob/master/docs/src/examples/formkit/ActualFormKit.vue
app.use(autoAnimatePlugin) // v-auto-animate="{ duration: 100 }"
app.use(plugin, formKitConfig)
app.mount('#app')
setupFormKit(app);
app.mount('#app');

View File

@ -1,79 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -220,7 +220,7 @@ onMounted(() => {
<div class="bg-gray-100 p-4 rounded-xl dark:bg-gray-800">
<h2 class="font-bold text-lg mb-4 color:gray-800 dark:text-white">Pro Inputs</h2>
<FormKit type="toggle" />
<!-- <FormKit type="toggle" /> -->
</div>
</FormKit>

View File

@ -2,8 +2,8 @@
export default {
prefix: '',
content: [
"./index.html",
"./src/**/*.vue",
// "./index.html",
// "./src/**/*.vue",
"./formkit.config.theme.ts",
"./formkit.config.ts",
],

View File

@ -1,12 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
@ -14,13 +14,17 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "vue",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
"paths": {
"@/*": ["src/*"],
"__fk-inputs__/*": ["src/__fk-inputs__/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@ -4,55 +4,8 @@ import {
presetUno,
transformerVariantGroup
} from "unocss";
import { fkLibrary } from "./formkit.config.fkLibrary";
import { classes, globals } from "./formkit.config.theme";
const familyList = [
...new Set(Object.values(fkLibrary).map((lib) => lib.family)),
].filter((f) => f) as string[];
const typeList = [
...new Set(Object.values(fkLibrary).map((lib) => lib.type)),
].filter((f) => f) as string[];
console.debug(`familyList :>> `, familyList);
console.debug(`typeList :>> `, typeList);
const globalsR = [
...new Set(Object.values(globals).flatMap((v) => Object.keys(v))),
];
const classesR = [
...new Set(
Object.keys(classes)
.filter(
(k) =>
familyList.some((f) => k.includes(f)) ||
typeList.some((t) => k.includes(t))
)
.map((k) => classes[k])
.flatMap((v) => Object.keys(v))
.filter((className) => !className.includes("[]"))
.filter((className) => !className.includes("&::"))
),
];
// console.debug(`classesR :>> `, classesR);
export default defineConfig({
content: {
filesystem: ["xx.ts"],
inline: [
"!border-none", // for: "range__inner"
classesR.join(" "),
globalsR.join(" "),
async () => {
return "";
},
],
pipeline: {
// include: [
// /xx.ts($|\?)/,
// ],
},
},
presets: [
presetUno({
dark: "media",

View File

@ -1,11 +1,27 @@
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { PrimeVueResolver } from '@primevue/auto-import-resolver';
import VueJsx from '@vitejs/plugin-vue-jsx';
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), UnoCSS(),],
plugins: [
vue(),
VueJsx(),
UnoCSS(),
Components({
resolvers: [PrimeVueResolver()],
}),
],
resolve: {
alias: {
'__fk-inputs__': '/src/__fk-inputs__',
'@': '/src',
},
},
build: {
sourcemap: true
}
})
sourcemap: true,
},
});

8
xx.ts
View File

@ -1,8 +0,0 @@
const classss = [
'text-8882',
'formkit-disabled:opacity-40',
]
const globals: Record<string, Record<string, boolean>> = {
};