Skip to content

Commit 02cf7a8

Browse files
committed
Merge branch 'reedsy-provide-decorator'
2 parents a10fbf1 + e7eca8d commit 02cf7a8

File tree

8 files changed

+160
-3
lines changed

8 files changed

+160
-3
lines changed

src/component.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineComponent, type ComponentCustomOptions } from 'vue';
2-
import { obtainSlot, getSuperSlot } from './utils'
2+
import { obtainSlot, getSuperSlot, getProviderFunction } from './utils'
33
import { build as optionSetup } from './option/setup'
44
import { build as optionComputed } from './option/computed'
55
import { build as optionData } from './option/data'
@@ -8,6 +8,7 @@ import { build as optionRef } from './option/ref'
88
import { build as optionWatch } from './option/watch'
99
import { build as optionProps } from './option/props'
1010
import { build as optionInject } from './option/inject'
11+
import { build as optionProvide } from './option/provide'
1112
import { build as optionEmit } from './option/emit'
1213
import { build as optionVModel } from './option/vmodel'
1314
import { build as optionAccessor } from './option/accessor'
@@ -45,6 +46,10 @@ function ComponentOption(cons: Cons, extend?: any) {
4546
watch: optionBuilder.watch,
4647
props: optionBuilder.props,
4748
inject: optionBuilder.inject,
49+
provide() {
50+
optionProvide(cons, optionBuilder, this)
51+
return optionBuilder.provide ?? {}
52+
},
4853
...optionBuilder.hooks,
4954
extends: extend
5055
}
@@ -73,7 +78,7 @@ function buildComponent(cons: Cons, arg: ComponentOption, extend?: any): any {
7378
const option = ComponentOption(cons, extend)
7479
const slot = obtainSlot(cons.prototype)
7580
Object.keys(arg).reduce<Record<string, any>>((option, name: string) => {
76-
if (['options', 'modifier', 'emits', 'setup'].includes(name)) {
81+
if (['options', 'modifier', 'emits', 'setup', 'provide'].includes(name)) {
7782
return option
7883
}
7984
option[name] = arg[name as keyof ComponentOption]
@@ -113,6 +118,13 @@ function buildComponent(cons: Cons, arg: ComponentOption, extend?: any): any {
113118
option.setup = setup
114119
}
115120

121+
//merge provide function
122+
const oldProvider = getProviderFunction(option.provide)
123+
const newProvider = getProviderFunction(arg.provide)
124+
option.provide = function() {
125+
return Object.assign({}, oldProvider.call(this), newProvider.call(this))
126+
}
127+
116128
//custom decorator
117129
const map = slot.getMap('customDecorator')
118130
if (map && map.size > 0) {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { decorator as Setup } from './option/setup'
33
export { decorator as Ref } from './option/ref'
44
export { decorator as Watch } from './option/watch'
55
export { decorator as Prop } from './option/props'
6+
export { decorator as Provide } from './option/provide'
67
export { decorator as Inject } from './option/inject'
78
export { decorator as Emit } from './option/emit'
89
export { decorator as VModel, decorator as Model } from './option/vmodel'

src/option/provide.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Cons } from '../component';
2+
import type { OptionBuilder } from '../optionBuilder'
3+
import { obtainSlot, optionNullableMemberDecorator } from '../utils'
4+
5+
export type ProvideConfig = null | string
6+
7+
export const decorator = optionNullableMemberDecorator(function (proto: any, name: string, key?: ProvideConfig) {
8+
const slot = obtainSlot(proto)
9+
const map = slot.obtainMap('provide')
10+
map.set(name, typeof key === 'undefined' ? null : key)
11+
})
12+
13+
export function build(cons: Cons, optionBuilder: OptionBuilder, vueInstance: any) {
14+
optionBuilder.provide ??= {}
15+
const sample = new cons(optionBuilder, vueInstance) as any
16+
const slot = obtainSlot(cons.prototype)
17+
const names = slot.obtainMap('provide')
18+
if (!names) return null
19+
names.forEach((value, name) => {
20+
const key = value === null ? name : value
21+
optionBuilder.provide![key] = sample[name]
22+
})
23+
}

src/optionBuilder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface OptionBuilder {
1111
computed?: Record<string, any>
1212
watch?: Record<string, WatchConfig | WatchConfig[]>
1313
props?: Record<string, PropsConfig>
14+
provide?: Record<string, any>
1415
inject?: Record<string, InjectConfig>
1516
setup?: OptionSetupFunction
1617
beforeCreateCallbacks?: Function[]

src/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import type { WatchConfig } from "./option/watch";
99
import type { SetupConfig } from './option/setup'
1010
import type { Record as CustomDecoratorRecord } from './custom/custom'
1111
import type { RefConfig } from './option/ref';
12+
import type { ProvideConfig } from './option/provide';
1213
import { compatibleMemberDecorator } from './deco3/utils';
1314

1415
const SlotSymbol = Symbol('vue-facing-decorator-slot')
1516

1617
export type SlotMapTypes = {
1718
vanilla: Map<string, boolean>
1819
computed: Map<string, boolean>
20+
provide: Map<string, ProvideConfig>
1921
inject: Map<string, InjectConfig>
2022
emit: Map<string, EmitConfig>
2123
emits: Map<string, boolean>
@@ -171,3 +173,8 @@ export function optionNullableMemberDecorator<T>(handler: { (proto: any, name: s
171173

172174
return decorator
173175
}
176+
177+
export function getProviderFunction(provide: any): () => {} {
178+
if (typeof provide === 'function') return provide
179+
return function () { return provide || {} }
180+
}

test/component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const FullOptionOpt = {
1515
name: 'FullOption',
1616
emits: ['emits1', 'emits2'],
1717
provide: function () {
18-
return 'provided'
18+
return {provided: 'provided'}
1919
},
2020
components: {
2121
empty: Empty
@@ -100,6 +100,9 @@ describe('Component',
100100
const r = await pro
101101
expect('setupVA').to.equal(r.setupA)
102102
break
103+
case 'provide':
104+
expect(opt()).to.eql({provided: 'provided'})
105+
break
103106
default:
104107
expect(opt).to.equal(FullOptionContext[key])
105108
}

test/option/provide.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
2+
import { expect } from 'chai';
3+
import 'mocha';
4+
import { Component, Provide, Base, Inject } from '../../dist'
5+
import {VueWrapper, mount} from '@vue/test-utils';
6+
7+
describe('decorator Provide',
8+
() => {
9+
@Component({
10+
template: '<div><slot/></div>',
11+
provide: {
12+
fromClassDecorator: 'provided from class decorator'
13+
},
14+
})
15+
class Parent extends Base {
16+
@Provide
17+
readonly foo = 'provided foo'
18+
19+
@Provide
20+
readonly foo2 = this.foo
21+
22+
@Provide('overridden')
23+
readonly _internalName = 123
24+
}
25+
26+
@Component({template: '<span />'})
27+
class Child extends Base {
28+
@Inject
29+
readonly fromClassDecorator!: string
30+
31+
@Inject
32+
readonly foo!: string
33+
34+
@Inject
35+
readonly foo2!: string
36+
37+
@Inject
38+
readonly overridden!: number
39+
}
40+
41+
let child: VueWrapper<Child>;
42+
43+
beforeEach(() => {
44+
const component = mount({
45+
template: `
46+
<Parent>
47+
<Child />
48+
</Parent>
49+
`,
50+
components: {
51+
Parent,
52+
Child,
53+
}
54+
})
55+
child = component.findComponent(Child)
56+
})
57+
58+
it('@Component decorator', () => {
59+
expect(child.vm.fromClassDecorator).to.equal('provided from class decorator')
60+
})
61+
62+
it('default key', () => {
63+
expect(child.vm.foo).to.equal('provided foo')
64+
})
65+
66+
it('accessing this', () => {
67+
expect(child.vm.foo2).to.equal('provided foo')
68+
})
69+
70+
it('custom key', () => {
71+
expect(child.vm).to.not.have.property('_internalName')
72+
expect(child.vm.overridden).to.equal(123)
73+
})
74+
75+
it('prioritises the class decorator', () => {
76+
@Component({
77+
template: '<div><slot/></div>',
78+
provide: {
79+
ambiguous: 'class'
80+
},
81+
})
82+
class Parent extends Base {
83+
@Provide
84+
readonly ambiguous = 'property'
85+
}
86+
87+
@Component({template: '<span />'})
88+
class Child extends Base {
89+
@Inject
90+
readonly ambiguous!: string
91+
}
92+
93+
const component = mount({
94+
template: `
95+
<Parent>
96+
<Child />
97+
</Parent>
98+
`,
99+
components: {
100+
Parent,
101+
Child,
102+
}
103+
})
104+
const child = component.findComponent(Child)
105+
106+
expect(child.vm.ambiguous).to.equal('class')
107+
})
108+
}
109+
)

test/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import './option/ref'
1111
import './option/props'
1212
import './option/watch'
1313
import './option/inject'
14+
import './option/provide'
1415
import './option/vmodel'
1516
import './option/accessor'
1617
import './feature/hooks'

0 commit comments

Comments
 (0)