From e6cca41e54f4405010cf8654313fb24cad36202a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 16:22:32 +0100 Subject: [PATCH] Re-expose `el` (#1230) * re-expose `el` We used to expose a custom `el` when we used a `setup()` and `render()` function. But due to some refactors we got now only have a `setup()` and no more `el`. This causes some problems if people relied on the exposed `el`. In this PR will make sure to re-expose the `el` that we used to expose. The only issue is, now that we manually expose a list, we have to re-expose the `$el` internal as well. * update changelog --- CHANGELOG.md | 1 + .../src/components/combobox/combobox.ts | 19 ++++++++--- .../src/components/dialog/dialog.ts | 4 ++- .../src/components/disclosure/disclosure.ts | 10 ++++-- .../src/components/focus-trap/focus-trap.ts | 4 ++- .../src/components/listbox/listbox.ts | 12 +++++-- .../src/components/menu/menu.ts | 12 +++++-- .../src/components/popover/popover.ts | 16 ++++++--- .../src/components/radio-group/radio-group.ts | 8 +++-- .../src/components/switch/switch.ts | 4 ++- .../src/components/tabs/tabs.ts | 34 +++++++++++-------- .../src/components/transitions/transition.ts | 4 ++- 12 files changed, 90 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953b9c91da..393dbb1651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve some internal code ([#1221](https://github.com/tailwindlabs/headlessui/pull/1221)) - Don’t drop initial character when searching in Combobox ([#1223](https://github.com/tailwindlabs/headlessui/pull/1223)) - Use `ownerDocument` instead of `document` ([#1158](https://github.com/tailwindlabs/headlessui/pull/1158)) +- Re-expose `el` ([#1230](https://github.com/tailwindlabs/headlessui/pull/1230)) ### Added diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index f85a616a3e..06658265e6 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -378,10 +378,12 @@ export let ComboboxButton = defineComponent({ props: { as: { type: [Object, String], default: 'button' }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useComboboxContext('ComboboxButton') let id = `headlessui-combobox-button-${useId()}` + expose({ el: api.buttonRef, $el: api.buttonRef }) + function handleClick(event: MouseEvent) { if (api.disabled.value) return if (api.comboboxState.value === ComboboxStates.Open) { @@ -494,11 +496,13 @@ export let ComboboxInput = defineComponent({ emits: { change: (_value: Event & { target: HTMLInputElement }) => true, }, - setup(props, { emit, attrs, slots }) { + setup(props, { emit, attrs, slots, expose }) { let api = useComboboxContext('ComboboxInput') let id = `headlessui-combobox-input-${useId()}` api.inputPropsRef = computed(() => props) + expose({ el: api.inputRef, $el: api.inputRef }) + function handleKeyDown(event: KeyboardEvent) { switch (event.key) { // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12 @@ -620,15 +624,20 @@ export let ComboboxOptions = defineComponent({ unmount: { type: Boolean, default: true }, hold: { type: [Boolean], default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useComboboxContext('ComboboxOptions') let id = `headlessui-combobox-options-${useId()}` + + expose({ el: api.optionsRef, $el: api.optionsRef }) + watchEffect(() => { api.optionsPropsRef.value.static = props.static }) + watchEffect(() => { api.optionsPropsRef.value.hold = props.hold }) + let usesOpenClosedState = useOpenClosed() let visible = computed(() => { if (usesOpenClosedState !== null) { @@ -685,11 +694,13 @@ export let ComboboxOption = defineComponent({ value: { type: [Object, String, Number, Boolean] }, disabled: { type: Boolean, default: false }, }, - setup(props, { slots, attrs }) { + setup(props, { slots, attrs, expose }) { let api = useComboboxContext('ComboboxOption') let id = `headlessui-combobox-option-${useId()}` let internalOptionRef = ref(null) + expose({ el: internalOptionRef, $el: internalOptionRef }) + let active = computed(() => { return api.activeOptionIndex.value !== null ? api.options.value[api.activeOptionIndex.value].id === id diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.ts b/packages/@headlessui-vue/src/components/dialog/dialog.ts index df8c9b6066..886f972502 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.ts @@ -75,7 +75,7 @@ export let Dialog = defineComponent({ initialFocus: { type: Object as PropType, default: null }, }, emits: { close: (_close: boolean) => true }, - setup(props, { emit, attrs, slots }) { + setup(props, { emit, attrs, slots, expose }) { let nestedDialogCount = ref(0) let usesOpenClosedState = useOpenClosed() @@ -94,6 +94,8 @@ export let Dialog = defineComponent({ let internalDialogRef = ref(null) let ownerDocument = computed(() => getOwnerDocument(internalDialogRef)) + expose({ el: internalDialogRef, $el: internalDialogRef }) + // Validations let hasOpen = props.open !== Missing || usesOpenClosedState !== null diff --git a/packages/@headlessui-vue/src/components/disclosure/disclosure.ts b/packages/@headlessui-vue/src/components/disclosure/disclosure.ts index 73c96fd1cc..d68e3ef0de 100644 --- a/packages/@headlessui-vue/src/components/disclosure/disclosure.ts +++ b/packages/@headlessui-vue/src/components/disclosure/disclosure.ts @@ -133,13 +133,15 @@ export let DisclosureButton = defineComponent({ as: { type: [Object, String], default: 'button' }, disabled: { type: [Boolean], default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useDisclosureContext('DisclosureButton') let panelContext = useDisclosurePanelContext() let isWithinPanel = panelContext === null ? false : panelContext === api.panelId - let internalButtonRef = ref(null) + let internalButtonRef = ref(null) + + expose({ el: internalButtonRef, $el: internalButtonRef }) if (!isWithinPanel) { watchEffect(() => { @@ -240,9 +242,11 @@ export let DisclosurePanel = defineComponent({ static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useDisclosureContext('DisclosurePanel') + expose({ el: api.panel, $el: api.panel }) + provide(DisclosurePanelContext, api.panelId) let usesOpenClosedState = useOpenClosed() diff --git a/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts b/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts index c9de90e9c3..e4d14cc1df 100644 --- a/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts +++ b/packages/@headlessui-vue/src/components/focus-trap/focus-trap.ts @@ -15,9 +15,11 @@ export let FocusTrap = defineComponent({ as: { type: [Object, String], default: 'div' }, initialFocus: { type: Object as PropType, default: null }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let container = ref(null) + expose({ el: container, $el: container }) + let focusTrapOptions = computed(() => ({ initialFocus: ref(props.initialFocus) })) useFocusTrap(container, FocusTrap.All, focusTrapOptions) diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.ts b/packages/@headlessui-vue/src/components/listbox/listbox.ts index bf4e8321d8..76323965ee 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.ts +++ b/packages/@headlessui-vue/src/components/listbox/listbox.ts @@ -351,10 +351,12 @@ export let ListboxButton = defineComponent({ props: { as: { type: [Object, String], default: 'button' }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useListboxContext('ListboxButton') let id = `headlessui-listbox-button-${useId()}` + expose({ el: api.buttonRef, $el: api.buttonRef }) + function handleKeyDown(event: KeyboardEvent) { switch (event.key) { // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-13 @@ -450,11 +452,13 @@ export let ListboxOptions = defineComponent({ static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useListboxContext('ListboxOptions') let id = `headlessui-listbox-options-${useId()}` let searchDebounce = ref | null>(null) + expose({ el: api.optionsRef, $el: api.optionsRef }) + function handleKeyDown(event: KeyboardEvent) { if (searchDebounce.value) clearTimeout(searchDebounce.value) @@ -572,11 +576,13 @@ export let ListboxOption = defineComponent({ value: { type: [Object, String, Number, Boolean] }, disabled: { type: Boolean, default: false }, }, - setup(props, { slots, attrs }) { + setup(props, { slots, attrs, expose }) { let api = useListboxContext('ListboxOption') let id = `headlessui-listbox-option-${useId()}` let internalOptionRef = ref(null) + expose({ el: internalOptionRef, $el: internalOptionRef }) + let active = computed(() => { return api.activeOptionIndex.value !== null ? api.options.value[api.activeOptionIndex.value].id === id diff --git a/packages/@headlessui-vue/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts index 24051b5d5c..690d5548ff 100644 --- a/packages/@headlessui-vue/src/components/menu/menu.ts +++ b/packages/@headlessui-vue/src/components/menu/menu.ts @@ -237,10 +237,12 @@ export let MenuButton = defineComponent({ disabled: { type: Boolean, default: false }, as: { type: [Object, String], default: 'button' }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useMenuContext('MenuButton') let id = `headlessui-menu-button-${useId()}` + expose({ el: api.buttonRef, $el: api.buttonRef }) + function handleKeyDown(event: KeyboardEvent) { switch (event.key) { // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-13 @@ -330,11 +332,13 @@ export let MenuItems = defineComponent({ static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useMenuContext('MenuItems') let id = `headlessui-menu-items-${useId()}` let searchDebounce = ref | null>(null) + expose({ el: api.itemsRef, $el: api.itemsRef }) + useTreeWalker({ container: computed(() => dom(api.itemsRef)), enabled: computed(() => api.menuState.value === MenuStates.Open), @@ -474,11 +478,13 @@ export let MenuItem = defineComponent({ as: { type: [Object, String], default: 'template' }, disabled: { type: Boolean, default: false }, }, - setup(props, { slots, attrs }) { + setup(props, { slots, attrs, expose }) { let api = useMenuContext('MenuItem') let id = `headlessui-menu-item-${useId()}` let internalItemRef = ref(null) + expose({ el: internalItemRef, $el: internalItemRef }) + let active = computed(() => { return api.activeItemIndex.value !== null ? api.items.value[api.activeItemIndex.value].id === id diff --git a/packages/@headlessui-vue/src/components/popover/popover.ts b/packages/@headlessui-vue/src/components/popover/popover.ts index fc77499efe..df05064083 100644 --- a/packages/@headlessui-vue/src/components/popover/popover.ts +++ b/packages/@headlessui-vue/src/components/popover/popover.ts @@ -92,12 +92,14 @@ export let Popover = defineComponent({ props: { as: { type: [Object, String], default: 'div' }, }, - setup(props, { slots, attrs }) { + setup(props, { slots, attrs, expose }) { let buttonId = `headlessui-popover-button-${useId()}` let panelId = `headlessui-popover-panel-${useId()}` let internalPopoverRef = ref(null) + expose({ el: internalPopoverRef, $el: internalPopoverRef }) + let popoverState = ref(PopoverStates.Closed) let button = ref(null) let panel = ref(null) @@ -217,10 +219,12 @@ export let PopoverButton = defineComponent({ as: { type: [Object, String], default: 'button' }, disabled: { type: [Boolean], default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = usePopoverContext('PopoverButton') let ownerDocument = computed(() => getOwnerDocument(api.button)) + expose({ el: api.button, $el: api.button }) + let groupContext = usePopoverGroupContext() let closeOthers = groupContext?.closeOthers @@ -462,11 +466,13 @@ export let PopoverPanel = defineComponent({ unmount: { type: Boolean, default: true }, focus: { type: Boolean, default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let { focus } = props let api = usePopoverContext('PopoverPanel') let ownerDocument = computed(() => getOwnerDocument(api.panel)) + expose({ el: api.panel, $el: api.panel }) + provide(PopoverPanelContext, api.panelId) onUnmounted(() => { @@ -599,11 +605,13 @@ export let PopoverGroup = defineComponent({ props: { as: { type: [Object, String], default: 'div' }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let groupRef = ref(null) let popovers = ref([]) let ownerDocument = computed(() => getOwnerDocument(groupRef)) + expose({ el: groupRef, $el: groupRef }) + function unregisterPopover(registerBag: PopoverRegisterBag) { let idx = popovers.value.indexOf(registerBag) if (idx !== -1) popovers.value.splice(idx, 1) diff --git a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts index deaf751875..963240d287 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -72,12 +72,14 @@ export let RadioGroup = defineComponent({ modelValue: { type: [Object, String, Number, Boolean] }, name: { type: String, optional: true }, }, - setup(props, { emit, attrs, slots }) { + setup(props, { emit, attrs, slots, expose }) { let radioGroupRef = ref(null) let options = ref([]) let labelledby = useLabels({ name: 'RadioGroupLabel' }) let describedby = useDescriptions({ name: 'RadioGroupDescription' }) + expose({ el: radioGroupRef, $el: radioGroupRef }) + let value = computed(() => props.modelValue) let api = { @@ -247,7 +249,7 @@ export let RadioGroupOption = defineComponent({ value: { type: [Object, String, Number, Boolean] }, disabled: { type: Boolean, default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useRadioGroupContext('RadioGroupOption') let id = `headlessui-radiogroup-option-${useId()}` let labelledby = useLabels({ name: 'RadioGroupLabel' }) @@ -257,6 +259,8 @@ export let RadioGroupOption = defineComponent({ let propsRef = computed(() => ({ value: props.value, disabled: props.disabled })) let state = ref(OptionState.Empty) + expose({ el: optionRef, $el: optionRef }) + onMounted(() => api.registerOption({ id, element: optionRef, propsRef })) onUnmounted(() => api.unregisterOption(id)) diff --git a/packages/@headlessui-vue/src/components/switch/switch.ts b/packages/@headlessui-vue/src/components/switch/switch.ts index 383cc15bc8..590321824c 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.ts +++ b/packages/@headlessui-vue/src/components/switch/switch.ts @@ -70,7 +70,7 @@ export let Switch = defineComponent({ value: { type: String, optional: true }, }, - setup(props, { emit, attrs, slots }) { + setup(props, { emit, attrs, slots, expose }) { let api = inject(GroupContext, null) let id = `headlessui-switch-${useId()}` @@ -85,6 +85,8 @@ export let Switch = defineComponent({ switchRef ) + expose({ el: switchRef, $el: switchRef }) + function handleClick(event: MouseEvent) { event.preventDefault() toggle() diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.ts b/packages/@headlessui-vue/src/components/tabs/tabs.ts index 9d67058df2..1ce70cc16a 100644 --- a/packages/@headlessui-vue/src/components/tabs/tabs.ts +++ b/packages/@headlessui-vue/src/components/tabs/tabs.ts @@ -181,16 +181,18 @@ export let Tab = defineComponent({ as: { type: [Object, String], default: 'button' }, disabled: { type: [Boolean], default: false }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useTabsContext('Tab') let id = `headlessui-tabs-tab-${useId()}` - let tabRef = ref() + let internalTabRef = ref(null) + + expose({ el: internalTabRef, $el: internalTabRef }) - onMounted(() => api.registerTab(tabRef)) - onUnmounted(() => api.unregisterTab(tabRef)) + onMounted(() => api.registerTab(internalTabRef)) + onUnmounted(() => api.unregisterTab(internalTabRef)) - let myIndex = computed(() => api.tabs.value.indexOf(tabRef)) + let myIndex = computed(() => api.tabs.value.indexOf(internalTabRef)) let selected = computed(() => myIndex.value === api.selectedIndex.value) function handleKeyDown(event: KeyboardEvent) { @@ -235,13 +237,13 @@ export let Tab = defineComponent({ } function handleFocus() { - dom(tabRef)?.focus() + dom(internalTabRef)?.focus() } function handleSelection() { if (props.disabled) return - dom(tabRef)?.focus() + dom(internalTabRef)?.focus() api.setSelectedIndex(myIndex.value) } @@ -254,13 +256,13 @@ export let Tab = defineComponent({ let type = useResolveButtonType( computed(() => ({ as: props.as, type: attrs.type })), - tabRef + internalTabRef ) return () => { let slot = { selected: selected.value } let propsWeControl = { - ref: tabRef, + ref: internalTabRef, onKeydown: handleKeyDown, onFocus: api.activation.value === 'manual' ? handleFocus : handleSelection, onMousedown: handleMouseDown, @@ -316,22 +318,24 @@ export let TabPanel = defineComponent({ static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true }, }, - setup(props, { attrs, slots }) { + setup(props, { attrs, slots, expose }) { let api = useTabsContext('TabPanel') let id = `headlessui-tabs-panel-${useId()}` - let panelRef = ref() + let internalPanelRef = ref(null) + + expose({ el: internalPanelRef, $el: internalPanelRef }) - onMounted(() => api.registerPanel(panelRef)) - onUnmounted(() => api.unregisterPanel(panelRef)) + onMounted(() => api.registerPanel(internalPanelRef)) + onUnmounted(() => api.unregisterPanel(internalPanelRef)) - let myIndex = computed(() => api.panels.value.indexOf(panelRef)) + let myIndex = computed(() => api.panels.value.indexOf(internalPanelRef)) let selected = computed(() => myIndex.value === api.selectedIndex.value) return () => { let slot = { selected: selected.value } let propsWeControl = { - ref: panelRef, + ref: internalPanelRef, id, role: 'tabpanel', 'aria-labelledby': api.tabs.value[myIndex.value]?.value?.id, diff --git a/packages/@headlessui-vue/src/components/transitions/transition.ts b/packages/@headlessui-vue/src/components/transitions/transition.ts index 87748dc9b8..dc967707c7 100644 --- a/packages/@headlessui-vue/src/components/transitions/transition.ts +++ b/packages/@headlessui-vue/src/components/transitions/transition.ts @@ -152,7 +152,7 @@ export let TransitionChild = defineComponent({ beforeLeave: () => true, afterLeave: () => true, }, - setup(props, { emit, attrs, slots }) { + setup(props, { emit, attrs, slots, expose }) { if (!hasTransitionContext() && hasOpenClosed()) { return () => h( @@ -172,6 +172,8 @@ export let TransitionChild = defineComponent({ let state = ref(TreeStates.Visible) let strategy = computed(() => (props.unmount ? RenderStrategy.Unmount : RenderStrategy.Hidden)) + expose({ el: container, $el: container }) + let { show, appear } = useTransitionContext() let { register, unregister } = useParentNesting()