Skip to content

Commit f1d4050

Browse files
committed
fix(addons): merge floating label extensions instead of overwriting
The FloatingLabel plugin was overwriting existing attributes on the outer and label sections, preventing other plugins from adding data attributes to those sections. Now uses extend() to merge attributes properly. Fixes #1619
1 parent cffd1f1 commit f1d4050

2 files changed

Lines changed: 146 additions & 3 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { h } from 'vue'
2+
import { mount } from '@vue/test-utils'
3+
import { FormKit, plugin, defaultConfig, resetCount } from '@formkit/vue'
4+
import { createFloatingLabelsPlugin } from '../src/plugins/floatingLabels/floatingLabelsPlugin'
5+
import { FormKitNode, FormKitPlugin, FormKitSectionsSchema } from '@formkit/core'
6+
import { clone } from '@formkit/utils'
7+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
8+
9+
/**
10+
* A test plugin that adds a custom data attribute to the outer section.
11+
* Used to verify that the floating labels plugin merges with existing attributes.
12+
*/
13+
function createTestAttributePlugin(): FormKitPlugin {
14+
return (node: FormKitNode) => {
15+
node.on('created', () => {
16+
if (!node.props || !node.props.definition) return
17+
18+
const inputDefinition = clone(node.props.definition)
19+
const originalSchema = inputDefinition.schema
20+
if (typeof originalSchema !== 'function') return
21+
22+
const higherOrderSchema = (extensions: FormKitSectionsSchema) => {
23+
extensions.outer = {
24+
attrs: {
25+
'data-test-plugin': 'true',
26+
},
27+
}
28+
return originalSchema(extensions)
29+
}
30+
31+
inputDefinition.schema = higherOrderSchema
32+
if (inputDefinition.schemaMemoKey) {
33+
inputDefinition.schemaMemoKey += '-test-plugin'
34+
}
35+
node.props.definition = inputDefinition
36+
})
37+
}
38+
}
39+
40+
describe('floatingLabels', () => {
41+
beforeEach(() => {
42+
resetCount()
43+
})
44+
45+
afterEach(() => {
46+
document.body.innerHTML = ''
47+
})
48+
49+
it('can mount a text input with floating labels', async () => {
50+
const wrapper = mount(FormKit, {
51+
props: {
52+
type: 'text',
53+
label: 'Test Label',
54+
floatingLabel: true,
55+
},
56+
attachTo: document.body,
57+
global: {
58+
plugins: [
59+
[
60+
plugin,
61+
defaultConfig({
62+
plugins: [createFloatingLabelsPlugin()],
63+
}),
64+
],
65+
],
66+
},
67+
})
68+
69+
await new Promise((r) => setTimeout(r, 10))
70+
expect(wrapper.html()).toContain('data-floating-label="true"')
71+
wrapper.unmount()
72+
})
73+
74+
it('merges with existing attributes from other plugins', async () => {
75+
// Plugin registration order determines schema execution order:
76+
// Plugins registered FIRST run LATER in the schema chain.
77+
// So if we want floating labels to merge with existing attrs,
78+
// floating labels must be registered FIRST (so it runs after the other plugin).
79+
const wrapper = mount(FormKit, {
80+
props: {
81+
type: 'text',
82+
label: 'Test Label',
83+
floatingLabel: true,
84+
},
85+
attachTo: document.body,
86+
global: {
87+
plugins: [
88+
[
89+
plugin,
90+
defaultConfig({
91+
plugins: [
92+
// Floating labels registered first -> runs later in schema chain
93+
// So it will see the attrs set by testPlugin and merge with them
94+
createFloatingLabelsPlugin(),
95+
// Test plugin registered second -> runs first in schema chain
96+
createTestAttributePlugin(),
97+
],
98+
}),
99+
],
100+
],
101+
},
102+
})
103+
104+
await new Promise((r) => setTimeout(r, 10))
105+
const html = wrapper.html()
106+
107+
// Both attributes should be present - floating labels merged with test plugin's attrs
108+
expect(html).toContain('data-floating-label="true"')
109+
expect(html).toContain('data-test-plugin="true"')
110+
wrapper.unmount()
111+
})
112+
113+
it('applies floating labels when useAsDefault is true', async () => {
114+
const wrapper = mount(FormKit, {
115+
props: {
116+
type: 'text',
117+
label: 'Test Label',
118+
},
119+
attachTo: document.body,
120+
global: {
121+
plugins: [
122+
[
123+
plugin,
124+
defaultConfig({
125+
plugins: [createFloatingLabelsPlugin({ useAsDefault: true })],
126+
}),
127+
],
128+
],
129+
},
130+
})
131+
132+
await new Promise((r) => setTimeout(r, 10))
133+
expect(wrapper.html()).toContain('data-floating-label="true"')
134+
wrapper.unmount()
135+
})
136+
})

packages/addons/src/plugins/floatingLabels/floatingLabelsPlugin.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
FormKitPlugin,
44
FormKitSectionsSchema,
55
} from '@formkit/core'
6-
import { clone, whenAvailable } from '@formkit/utils'
6+
import { clone, extend, isPojo, whenAvailable } from '@formkit/utils'
77
import { findSection } from '@formkit/inputs'
88

99
/**
@@ -107,12 +107,16 @@ export function createFloatingLabelsPlugin(
107107
const originalSchema = inputDefinition.schema
108108
if (typeof originalSchema !== 'function') return
109109
const higherOrderSchema = (extensions: FormKitSectionsSchema) => {
110-
extensions.outer = {
110+
const outerAttrs = {
111111
attrs: {
112112
'data-floating-label': 'true',
113113
},
114114
}
115-
extensions.label = {
115+
extensions.outer = isPojo(extensions.outer)
116+
? (extend(extensions.outer, outerAttrs, false, true) as (typeof extensions)['outer'])
117+
: outerAttrs
118+
119+
const labelAttrs = {
116120
attrs: {
117121
style: {
118122
if: '$_offsetCalculated',
@@ -121,6 +125,9 @@ export function createFloatingLabelsPlugin(
121125
},
122126
},
123127
}
128+
extensions.label = isPojo(extensions.label)
129+
? (extend(extensions.label, labelAttrs, false, true) as (typeof extensions)['label'])
130+
: labelAttrs
124131

125132
const inputSchema = originalSchema(extensions)
126133
const finalSchema = Array.isArray(inputSchema)

0 commit comments

Comments
 (0)