UNPKG

@headlessui/vue

Version:

A set of completely unstyled, fully accessible UI components for Vue 3, designed to integrate beautifully with Tailwind CSS.

1,680 lines (1,435 loc) • 154 kB
import { cloneVNode, h, onUnmounted, ref, watchEffect, onUpdated, inject, provide, defineComponent, Teleport, reactive, computed, unref, onMounted, nextTick, toRaw, watch } from 'vue'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } it = o[Symbol.iterator](); return it.next.bind(it); } function match(value, lookup) { if (value in lookup) { var returnValue = lookup[value]; for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } return typeof returnValue === 'function' ? returnValue.apply(void 0, args) : returnValue; } var error = new Error("Tried to handle \"" + value + "\" but there is no handler defined. Only defined handlers are: " + Object.keys(lookup).map(function (key) { return "\"" + key + "\""; }).join(', ') + "."); if (Error.captureStackTrace) Error.captureStackTrace(error, match); throw error; } var Features; (function (Features) { /** No features at all */ Features[Features["None"] = 0] = "None"; /** * When used, this will allow us to use one of the render strategies. * * **The render strategies are:** * - **Unmount** _(Will unmount the component.)_ * - **Hidden** _(Will hide the component using the [hidden] attribute.)_ */ Features[Features["RenderStrategy"] = 1] = "RenderStrategy"; /** * When used, this will allow the user of our component to be in control. This can be used when * you want to transition based on some state. */ Features[Features["Static"] = 2] = "Static"; })(Features || (Features = {})); var RenderStrategy; (function (RenderStrategy) { RenderStrategy[RenderStrategy["Unmount"] = 0] = "Unmount"; RenderStrategy[RenderStrategy["Hidden"] = 1] = "Hidden"; })(RenderStrategy || (RenderStrategy = {})); function render(_ref) { var _ref$visible = _ref.visible, visible = _ref$visible === void 0 ? true : _ref$visible, _ref$features = _ref.features, features = _ref$features === void 0 ? Features.None : _ref$features, main = _objectWithoutPropertiesLoose(_ref, ["visible", "features"]); // Visible always render if (visible) return _render(main); if (features & Features.Static) { // When the `static` prop is passed as `true`, then the user is in control, thus we don't care about anything else if (main.props["static"]) return _render(main); } if (features & Features.RenderStrategy) { var _main$props$unmount, _match; var strategy = ((_main$props$unmount = main.props.unmount) != null ? _main$props$unmount : true) ? RenderStrategy.Unmount : RenderStrategy.Hidden; return match(strategy, (_match = {}, _match[RenderStrategy.Unmount] = function () { return null; }, _match[RenderStrategy.Hidden] = function () { return _render(_extends({}, main, { props: _extends({}, main.props, { hidden: true, style: { display: 'none' } }) })); }, _match)); } // No features enabled, just render return _render(main); } function _render(_ref2) { var props = _ref2.props, attrs = _ref2.attrs, slots = _ref2.slots, slot = _ref2.slot, name = _ref2.name; var _omit = omit(props, ['unmount', 'static']), as = _omit.as, passThroughProps = _objectWithoutPropertiesLoose(_omit, ["as"]); var children = slots["default"] == null ? void 0 : slots["default"](slot); if (as === 'template') { if (Object.keys(passThroughProps).length > 0 || Object.keys(attrs).length > 0) { var _ref3 = children != null ? children : [], firstChild = _ref3[0], other = _ref3.slice(1); if (!isValidElement(firstChild) || other.length > 0) { throw new Error(['Passing props on "template"!', '', "The current component <" + name + " /> is rendering a \"template\".", "However we need to passthrough the following props:", Object.keys(passThroughProps).concat(Object.keys(attrs)).map(function (line) { return " - " + line; }).join('\n'), '', 'You can apply a few solutions:', ['Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".', 'Render a single element as the child so that we can forward the props onto that element.'].map(function (line) { return " - " + line; }).join('\n')].join('\n')); } return cloneVNode(firstChild, passThroughProps); } if (Array.isArray(children) && children.length === 1) { return children[0]; } return children; } return h(as, passThroughProps, children); } function omit(object, keysToOmit) { if (keysToOmit === void 0) { keysToOmit = []; } var clone = Object.assign({}, object); for (var _iterator = _createForOfIteratorHelperLoose(keysToOmit), _step; !(_step = _iterator()).done;) { var key = _step.value; if (key in clone) delete clone[key]; } return clone; } function isValidElement(input) { if (input == null) return false; // No children if (typeof input.type === 'string') return true; // 'div', 'span', ... if (typeof input.type === 'object') return true; // Other components if (typeof input.type === 'function') return true; // Built-ins like Transition return false; // Comments, strings, ... } // TODO: This must already exist somewhere, right? 🤔 // Ref: https://www.w3.org/TR/uievents-key/#named-key-attribute-values var Keys; (function (Keys) { Keys["Space"] = " "; Keys["Enter"] = "Enter"; Keys["Escape"] = "Escape"; Keys["Backspace"] = "Backspace"; Keys["ArrowLeft"] = "ArrowLeft"; Keys["ArrowUp"] = "ArrowUp"; Keys["ArrowRight"] = "ArrowRight"; Keys["ArrowDown"] = "ArrowDown"; Keys["Home"] = "Home"; Keys["End"] = "End"; Keys["PageUp"] = "PageUp"; Keys["PageDown"] = "PageDown"; Keys["Tab"] = "Tab"; })(Keys || (Keys = {})); var id = 0; function generateId() { return ++id; } function useId() { return generateId(); } // - https://stackoverflow.com/a/30753870 var focusableSelector = /*#__PURE__*/['[contentEditable=true]', '[tabindex]', 'a[href]', 'area[href]', 'button:not([disabled])', 'iframe', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])'].map(process.env.NODE_ENV === 'test' ? // TODO: Remove this once JSDOM fixes the issue where an element that is // "hidden" can be the document.activeElement, because this is not possible // in real browsers. // TODO: Remove this once JSDOM fixes the issue where an element that is function (selector) { return selector + ":not([tabindex='-1']):not([style*='display: none'])"; } : function (selector) { return selector + ":not([tabindex='-1'])"; }).join(','); var Focus; (function (Focus) { /** Focus the first non-disabled element */ Focus[Focus["First"] = 1] = "First"; /** Focus the previous non-disabled element */ Focus[Focus["Previous"] = 2] = "Previous"; /** Focus the next non-disabled element */ Focus[Focus["Next"] = 4] = "Next"; /** Focus the last non-disabled element */ Focus[Focus["Last"] = 8] = "Last"; /** Wrap tab around */ Focus[Focus["WrapAround"] = 16] = "WrapAround"; /** Prevent scrolling the focusable elements into view */ Focus[Focus["NoScroll"] = 32] = "NoScroll"; })(Focus || (Focus = {})); var FocusResult; (function (FocusResult) { FocusResult[FocusResult["Error"] = 0] = "Error"; FocusResult[FocusResult["Overflow"] = 1] = "Overflow"; FocusResult[FocusResult["Success"] = 2] = "Success"; FocusResult[FocusResult["Underflow"] = 3] = "Underflow"; })(FocusResult || (FocusResult = {})); var Direction; (function (Direction) { Direction[Direction["Previous"] = -1] = "Previous"; Direction[Direction["Next"] = 1] = "Next"; })(Direction || (Direction = {})); function getFocusableElements(container) { if (container === void 0) { container = document.body; } if (container == null) return []; return Array.from(container.querySelectorAll(focusableSelector)); } var FocusableMode; (function (FocusableMode) { /** The element itself must be focusable. */ FocusableMode[FocusableMode["Strict"] = 0] = "Strict"; /** The element should be inside of a focusable element. */ FocusableMode[FocusableMode["Loose"] = 1] = "Loose"; })(FocusableMode || (FocusableMode = {})); function isFocusableElement(element, mode) { var _match; if (mode === void 0) { mode = FocusableMode.Strict; } if (element === document.body) return false; return match(mode, (_match = {}, _match[FocusableMode.Strict] = function () { return element.matches(focusableSelector); }, _match[FocusableMode.Loose] = function () { var next = element; while (next !== null) { if (next.matches(focusableSelector)) return true; next = next.parentElement; } return false; }, _match)); } function focusElement(element) { element == null ? void 0 : element.focus({ preventScroll: true }); } function focusIn(container, focus) { var elements = Array.isArray(container) ? container : getFocusableElements(container); var active = document.activeElement; var direction = function () { if (focus & (Focus.First | Focus.Next)) return Direction.Next; if (focus & (Focus.Previous | Focus.Last)) return Direction.Previous; throw new Error('Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last'); }(); var startIndex = function () { if (focus & Focus.First) return 0; if (focus & Focus.Previous) return Math.max(0, elements.indexOf(active)) - 1; if (focus & Focus.Next) return Math.max(0, elements.indexOf(active)) + 1; if (focus & Focus.Last) return elements.length - 1; throw new Error('Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last'); }(); var focusOptions = focus & Focus.NoScroll ? { preventScroll: true } : {}; var offset = 0; var total = elements.length; var next = undefined; do { var _next; // Guard against infinite loops if (offset >= total || offset + total <= 0) return FocusResult.Error; var nextIdx = startIndex + offset; if (focus & Focus.WrapAround) { nextIdx = (nextIdx + total) % total; } else { if (nextIdx < 0) return FocusResult.Underflow; if (nextIdx >= total) return FocusResult.Overflow; } next = elements[nextIdx]; // Try the focus the next element, might not work if it is "hidden" to the user. (_next = next) == null ? void 0 : _next.focus(focusOptions); // Try the next one in line offset += direction; } while (next !== document.activeElement); // This is a little weird, but let me try and explain: There are a few scenario's // in chrome for example where a focused `<a>` tag does not get the default focus // styles and sometimes they do. This highly depends on whether you started by // clicking or by using your keyboard. When you programmatically add focus `anchor.focus()` // then the active element (document.activeElement) is this anchor, which is expected. // However in that case the default focus styles are not applied *unless* you // also add this tabindex. if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0'); return FocusResult.Success; } function useWindowEvent(type, listener, options) { window.addEventListener(type, listener, options); onUnmounted(function () { return window.removeEventListener(type, listener, options); }); } function contains(containers, element) { for (var _iterator = _createForOfIteratorHelperLoose(containers), _step; !(_step = _iterator()).done;) { var container = _step.value; if (container.contains(element)) return true; } return false; } function useFocusTrap(containers, enabled, options) { if (enabled === void 0) { enabled = ref(true); } if (options === void 0) { options = ref({}); } var restoreElement = ref(typeof window !== 'undefined' ? document.activeElement : null); var previousActiveElement = ref(null); function handleFocus() { if (!enabled.value) return; if (containers.value.size !== 1) return; var initialFocus = options.value.initialFocus; var activeElement = document.activeElement; if (initialFocus) { if (initialFocus === activeElement) { return; // Initial focus ref is already the active element } } else if (contains(containers.value, activeElement)) { return; // Already focused within Dialog } restoreElement.value = activeElement; // Try to focus the initialFocus ref if (initialFocus) { focusElement(initialFocus); } else { var couldFocus = false; for (var _iterator = _createForOfIteratorHelperLoose(containers.value), _step; !(_step = _iterator()).done;) { var container = _step.value; var result = focusIn(container, Focus.First); if (result === FocusResult.Success) { couldFocus = true; break; } } if (!couldFocus) console.warn('There are no focusable elements inside the <FocusTrap />'); } previousActiveElement.value = document.activeElement; } // Restore when `enabled` becomes false function restore() { focusElement(restoreElement.value); restoreElement.value = null; previousActiveElement.value = null; } // Handle initial focus watchEffect(handleFocus); onUpdated(function () { enabled.value ? handleFocus() : restore(); }); onUnmounted(restore); // Handle Tab & Shift+Tab keyboard events useWindowEvent('keydown', function (event) { if (!enabled.value) return; if (event.key !== Keys.Tab) return; if (!document.activeElement) return; if (containers.value.size !== 1) return; event.preventDefault(); for (var _iterator2 = _createForOfIteratorHelperLoose(containers.value), _step2; !(_step2 = _iterator2()).done;) { var element = _step2.value; var result = focusIn(element, (event.shiftKey ? Focus.Previous : Focus.Next) | Focus.WrapAround); if (result === FocusResult.Success) { previousActiveElement.value = document.activeElement; break; } } }); // Prevent programmatically escaping useWindowEvent('focus', function (event) { if (!enabled.value) return; if (containers.value.size !== 1) return; var previous = previousActiveElement.value; if (!previous) return; var toElement = event.target; if (toElement && toElement instanceof HTMLElement) { if (!contains(containers.value, toElement)) { event.preventDefault(); event.stopPropagation(); focusElement(previous); } else { previousActiveElement.value = toElement; focusElement(toElement); } } else { focusElement(previousActiveElement.value); } }, true); } var CHILDREN_SELECTOR = process.env.NODE_ENV === 'test' ? '[data-v-app=""] > *' : 'body > *'; var interactables = /*#__PURE__*/new Set(); var originals = /*#__PURE__*/new Map(); function inert(element) { element.setAttribute('aria-hidden', 'true'); // @ts-expect-error `inert` does not exist on HTMLElement (yet!) element.inert = true; } function restore(element) { var original = originals.get(element); if (!original) return; if (original['aria-hidden'] === null) element.removeAttribute('aria-hidden');else element.setAttribute('aria-hidden', original['aria-hidden']); // @ts-expect-error `inert` does not exist on HTMLElement (yet!) element.inert = original.inert; } function useInertOthers(container, enabled) { if (enabled === void 0) { enabled = ref(true); } watchEffect(function (onInvalidate) { if (!enabled.value) return; if (!container.value) return; var element = container.value; // Mark myself as an interactable element interactables.add(element); // Restore elements that now contain an interactable child for (var _iterator = _createForOfIteratorHelperLoose(originals.keys()), _step; !(_step = _iterator()).done;) { var original = _step.value; if (original.contains(element)) { restore(original); originals["delete"](original); } } // Collect direct children of the body document.querySelectorAll(CHILDREN_SELECTOR).forEach(function (child) { if (!(child instanceof HTMLElement)) return; // Skip non-HTMLElements // Skip the interactables, and the parents of the interactables for (var _iterator2 = _createForOfIteratorHelperLoose(interactables), _step2; !(_step2 = _iterator2()).done;) { var interactable = _step2.value; if (child.contains(interactable)) return; } // Keep track of the elements if (interactables.size === 1) { originals.set(child, { 'aria-hidden': child.getAttribute('aria-hidden'), // @ts-expect-error `inert` does not exist on HTMLElement (yet!) inert: child.inert }); // Mutate the element inert(child); } }); onInvalidate(function () { // Inert is disabled on the current element interactables["delete"](element); // We still have interactable elements, therefore this one and its parent // will become inert as well. if (interactables.size > 0) { // Collect direct children of the body document.querySelectorAll(CHILDREN_SELECTOR).forEach(function (child) { if (!(child instanceof HTMLElement)) return; // Skip non-HTMLElements // Skip already inert parents if (originals.has(child)) return; // Skip the interactables, and the parents of the interactables for (var _iterator3 = _createForOfIteratorHelperLoose(interactables), _step3; !(_step3 = _iterator3()).done;) { var interactable = _step3.value; if (child.contains(interactable)) return; } originals.set(child, { 'aria-hidden': child.getAttribute('aria-hidden'), // @ts-expect-error `inert` does not exist on HTMLElement (yet!) inert: child.inert }); // Mutate the element inert(child); }); } else { for (var _iterator4 = _createForOfIteratorHelperLoose(originals.keys()), _step4; !(_step4 = _iterator4()).done;) { var _element = _step4.value; // Restore restore(_element); // Cleanup originals["delete"](_element); } } }); }); } var StackContext = /*#__PURE__*/Symbol('StackContext'); var StackMessage; (function (StackMessage) { StackMessage[StackMessage["AddElement"] = 0] = "AddElement"; StackMessage[StackMessage["RemoveElement"] = 1] = "RemoveElement"; })(StackMessage || (StackMessage = {})); function useStackContext() { return inject(StackContext, function () {}); } function useElemenStack(element) { var notify = useStackContext(); watchEffect(function (onInvalidate) { var domElement = element == null ? void 0 : element.value; if (!domElement) return; notify(StackMessage.AddElement, domElement); onInvalidate(function () { return notify(StackMessage.RemoveElement, domElement); }); }); } function useStackProvider(onUpdate) { var parentUpdate = useStackContext(); function notify() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } // Notify our layer onUpdate == null ? void 0 : onUpdate.apply(void 0, args); // Notify the parent parentUpdate.apply(void 0, args); } provide(StackContext, notify); } var ForcePortalRootContext = /*#__PURE__*/Symbol('ForcePortalRootContext'); function usePortalRoot() { return inject(ForcePortalRootContext, false); } var ForcePortalRoot = /*#__PURE__*/defineComponent({ name: 'ForcePortalRoot', props: { as: { type: [Object, String], "default": 'template' }, force: { type: Boolean, "default": false } }, setup: function setup(props, _ref) { var slots = _ref.slots, attrs = _ref.attrs; provide(ForcePortalRootContext, props.force); return function () { var passThroughProps = _objectWithoutPropertiesLoose(props, ["force"]); return render({ props: passThroughProps, slot: {}, slots: slots, attrs: attrs, name: 'ForcePortalRoot' }); }; } }); function getPortalRoot() { var existingRoot = document.getElementById('headlessui-portal-root'); if (existingRoot) return existingRoot; var root = document.createElement('div'); root.setAttribute('id', 'headlessui-portal-root'); return document.body.appendChild(root); } var Portal = /*#__PURE__*/defineComponent({ name: 'Portal', props: { as: { type: [Object, String], "default": 'div' } }, setup: function setup(props, _ref) { var slots = _ref.slots, attrs = _ref.attrs; var forcePortalRoot = usePortalRoot(); var groupContext = inject(PortalGroupContext, null); var myTarget = ref(forcePortalRoot === true ? getPortalRoot() : groupContext === null ? getPortalRoot() : groupContext.resolveTarget()); watchEffect(function () { if (forcePortalRoot) return; if (groupContext === null) return; myTarget.value = groupContext.resolveTarget(); }); var element = ref(null); useElemenStack(element); onUnmounted(function () { var root = document.getElementById('headlessui-portal-root'); if (!root) return; if (myTarget.value !== root) return; if (myTarget.value.children.length <= 0) { var _myTarget$value$paren; (_myTarget$value$paren = myTarget.value.parentElement) == null ? void 0 : _myTarget$value$paren.removeChild(myTarget.value); } }); useStackProvider(); return function () { if (myTarget.value === null) return null; var propsWeControl = { ref: element }; return h( // @ts-expect-error Children can be an object, but TypeScript is not happy // with it. Once this is fixed upstream we can remove this assertion. Teleport, { to: myTarget.value }, render({ props: _extends({}, props, propsWeControl), slot: {}, attrs: attrs, slots: slots, name: 'Portal' })); }; } }); // --- var PortalGroupContext = /*#__PURE__*/Symbol('PortalGroupContext'); var PortalGroup = /*#__PURE__*/defineComponent({ name: 'PortalGroup', props: { as: { type: [Object, String], "default": 'template' }, target: { type: Object, "default": null } }, setup: function setup(props, _ref2) { var attrs = _ref2.attrs, slots = _ref2.slots; var api = reactive({ resolveTarget: function resolveTarget() { return props.target; } }); provide(PortalGroupContext, api); return function () { var passThroughProps = _objectWithoutPropertiesLoose(props, ["target"]); return render({ props: passThroughProps, slot: {}, attrs: attrs, slots: slots, name: 'PortalGroup' }); }; } }); var DescriptionContext = /*#__PURE__*/Symbol('DescriptionContext'); function useDescriptionContext() { var context = inject(DescriptionContext, null); if (context === null) { throw new Error('Missing parent'); } return context; } function useDescriptions(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$slot = _ref.slot, slot = _ref$slot === void 0 ? ref({}) : _ref$slot, _ref$name = _ref.name, name = _ref$name === void 0 ? 'Description' : _ref$name, _ref$props = _ref.props, props = _ref$props === void 0 ? {} : _ref$props; var descriptionIds = ref([]); function register(value) { descriptionIds.value.push(value); return function () { var idx = descriptionIds.value.indexOf(value); if (idx === -1) return; descriptionIds.value.splice(idx, 1); }; } provide(DescriptionContext, { register: register, slot: slot, name: name, props: props }); // The actual id's as string or undefined. return computed(function () { return descriptionIds.value.length > 0 ? descriptionIds.value.join(' ') : undefined; }); } // --- var Description = /*#__PURE__*/defineComponent({ name: 'Description', props: { as: { type: [Object, String], "default": 'p' } }, render: function render$1() { var _this$context = this.context, _this$context$name = _this$context.name, name = _this$context$name === void 0 ? 'Description' : _this$context$name, _this$context$slot = _this$context.slot, slot = _this$context$slot === void 0 ? ref({}) : _this$context$slot, _this$context$props = _this$context.props, props = _this$context$props === void 0 ? {} : _this$context$props; var passThroughProps = this.$props; var propsWeControl = _extends({}, Object.entries(props).reduce(function (acc, _ref2) { var _Object$assign; var key = _ref2[0], value = _ref2[1]; return Object.assign(acc, (_Object$assign = {}, _Object$assign[key] = unref(value), _Object$assign)); }, {}), { id: this.id }); return render({ props: _extends({}, passThroughProps, propsWeControl), slot: slot.value, attrs: this.$attrs, slots: this.$slots, name: name }); }, setup: function setup() { var context = useDescriptionContext(); var id = "headlessui-description-" + useId(); onMounted(function () { return onUnmounted(context.register(id)); }); return { id: id, context: context }; } }); function dom(ref) { var _ref$value$$el; if (ref == null) return null; if (ref.value == null) return null; return (_ref$value$$el = ref.value.$el) != null ? _ref$value$$el : ref.value; } var Context = /*#__PURE__*/Symbol('Context'); var State; (function (State) { State[State["Open"] = 0] = "Open"; State[State["Closed"] = 1] = "Closed"; })(State || (State = {})); function hasOpenClosed() { return useOpenClosed() !== null; } function useOpenClosed() { return inject(Context, null); } function useOpenClosedProvider(value) { provide(Context, value); } var DialogStates; (function (DialogStates) { DialogStates[DialogStates["Open"] = 0] = "Open"; DialogStates[DialogStates["Closed"] = 1] = "Closed"; })(DialogStates || (DialogStates = {})); var DialogContext = /*#__PURE__*/Symbol('DialogContext'); function useDialogContext(component) { var context = inject(DialogContext, null); if (context === null) { var err = new Error("<" + component + " /> is missing a parent <Dialog /> component."); if (Error.captureStackTrace) Error.captureStackTrace(err, useDialogContext); throw err; } return context; } // --- var Missing = 'DC8F892D-2EBD-447C-A4C8-A03058436FF4'; var Dialog = /*#__PURE__*/defineComponent({ name: 'Dialog', inheritAttrs: false, props: { as: { type: [Object, String], "default": 'div' }, "static": { type: Boolean, "default": false }, unmount: { type: Boolean, "default": true }, open: { type: [Boolean, String], "default": Missing }, initialFocus: { type: Object, "default": null } }, emits: { close: function close(_close) { return true; } }, render: function render$1() { var _this = this; var propsWeControl = _extends({}, this.$attrs, { ref: 'el', id: this.id, role: 'dialog', 'aria-modal': this.dialogState === DialogStates.Open ? true : undefined, 'aria-labelledby': this.titleId, 'aria-describedby': this.describedby, onClick: this.handleClick }); var _this$$props = this.$props, passThroughProps = _objectWithoutPropertiesLoose(_this$$props, ["open", "initialFocus"]); var slot = { open: this.dialogState === DialogStates.Open }; return h(ForcePortalRoot, { force: true }, function () { return h(Portal, function () { return h(PortalGroup, { target: _this.dialogRef }, function () { return h(ForcePortalRoot, { force: false }, function () { return render({ props: _extends({}, passThroughProps, propsWeControl), slot: slot, attrs: _this.$attrs, slots: _this.$slots, visible: _this.visible, features: Features.RenderStrategy | Features.Static, name: 'Dialog' }); }); }); }); }); }, setup: function setup(props, _ref) { var emit = _ref.emit; var containers = ref(new Set()); var usesOpenClosedState = useOpenClosed(); var open = computed(function () { if (props.open === Missing && usesOpenClosedState !== null) { var _match; // Update the `open` prop based on the open closed state return match(usesOpenClosedState.value, (_match = {}, _match[State.Open] = true, _match[State.Closed] = false, _match)); } return props.open; }); // Validations var hasOpen = props.open !== Missing || usesOpenClosedState !== null; if (!hasOpen) { throw new Error("You forgot to provide an `open` prop to the `Dialog`."); } if (typeof open.value !== 'boolean') { throw new Error("You provided an `open` prop to the `Dialog`, but the value is not a boolean. Received: " + (open.value === Missing ? undefined : props.open)); } var dialogState = computed(function () { return props.open ? DialogStates.Open : DialogStates.Closed; }); var visible = computed(function () { if (usesOpenClosedState !== null) { return usesOpenClosedState.value === State.Open; } return dialogState.value === DialogStates.Open; }); var internalDialogRef = ref(null); var enabled = ref(dialogState.value === DialogStates.Open); onUpdated(function () { enabled.value = dialogState.value === DialogStates.Open; }); var id = "headlessui-dialog-" + useId(); var focusTrapOptions = computed(function () { return { initialFocus: props.initialFocus }; }); useFocusTrap(containers, enabled, focusTrapOptions); useInertOthers(internalDialogRef, enabled); useStackProvider(function (message, element) { var _match2; return match(message, (_match2 = {}, _match2[StackMessage.AddElement] = function () { containers.value.add(element); }, _match2[StackMessage.RemoveElement] = function () { containers.value["delete"](element); }, _match2)); }); var describedby = useDescriptions({ name: 'DialogDescription', slot: computed(function () { return { open: open.value }; }) }); var titleId = ref(null); var api = { titleId: titleId, dialogState: dialogState, setTitleId: function setTitleId(id) { if (titleId.value === id) return; titleId.value = id; }, close: function close() { emit('close', false); } }; provide(DialogContext, api); // Handle outside click useWindowEvent('mousedown', function (event) { var target = event.target; if (dialogState.value !== DialogStates.Open) return; if (containers.value.size !== 1) return; if (contains(containers.value, target)) return; api.close(); nextTick(function () { return target == null ? void 0 : target.focus(); }); }); // Handle `Escape` to close useWindowEvent('keydown', function (event) { if (event.key !== Keys.Escape) return; if (dialogState.value !== DialogStates.Open) return; if (containers.value.size > 1) return; // 1 is myself, otherwise other elements in the Stack event.preventDefault(); event.stopPropagation(); api.close(); }); // Scroll lock watchEffect(function (onInvalidate) { if (dialogState.value !== DialogStates.Open) return; var overflow = document.documentElement.style.overflow; var paddingRight = document.documentElement.style.paddingRight; var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; document.documentElement.style.overflow = 'hidden'; document.documentElement.style.paddingRight = scrollbarWidth + "px"; onInvalidate(function () { document.documentElement.style.overflow = overflow; document.documentElement.style.paddingRight = paddingRight; }); }); // Trigger close when the FocusTrap gets hidden watchEffect(function (onInvalidate) { if (dialogState.value !== DialogStates.Open) return; var container = dom(internalDialogRef); if (!container) return; var observer = new IntersectionObserver(function (entries) { for (var _iterator = _createForOfIteratorHelperLoose(entries), _step; !(_step = _iterator()).done;) { var entry = _step.value; if (entry.boundingClientRect.x === 0 && entry.boundingClientRect.y === 0 && entry.boundingClientRect.width === 0 && entry.boundingClientRect.height === 0) { api.close(); } } }); observer.observe(container); onInvalidate(function () { return observer.disconnect(); }); }); return { id: id, el: internalDialogRef, dialogRef: internalDialogRef, containers: containers, dialogState: dialogState, titleId: titleId, describedby: describedby, visible: visible, open: open, handleClick: function handleClick(event) { event.stopPropagation(); } }; } }); // --- var DialogOverlay = /*#__PURE__*/defineComponent({ name: 'DialogOverlay', props: { as: { type: [Object, String], "default": 'div' } }, render: function render$1() { var api = useDialogContext('DialogOverlay'); var propsWeControl = { ref: 'el', id: this.id, 'aria-hidden': true, onClick: this.handleClick }; var passThroughProps = this.$props; return render({ props: _extends({}, passThroughProps, propsWeControl), slot: { open: api.dialogState.value === DialogStates.Open }, attrs: this.$attrs, slots: this.$slots, name: 'DialogOverlay' }); }, setup: function setup() { var api = useDialogContext('DialogOverlay'); var id = "headlessui-dialog-overlay-" + useId(); return { id: id, handleClick: function handleClick(event) { event.preventDefault(); event.stopPropagation(); api.close(); } }; } }); // --- var DialogTitle = /*#__PURE__*/defineComponent({ name: 'DialogTitle', props: { as: { type: [Object, String], "default": 'h2' } }, render: function render$1() { var api = useDialogContext('DialogTitle'); var propsWeControl = { id: this.id }; var passThroughProps = this.$props; return render({ props: _extends({}, passThroughProps, propsWeControl), slot: { open: api.dialogState.value === DialogStates.Open }, attrs: this.$attrs, slots: this.$slots, name: 'DialogTitle' }); }, setup: function setup() { var api = useDialogContext('DialogTitle'); var id = "headlessui-dialog-title-" + useId(); onMounted(function () { api.setTitleId(id); onUnmounted(function () { return api.setTitleId(null); }); }); return { id: id }; } }); // --- var DialogDescription = Description; function resolveType(type, as) { if (type) return type; var tag = as != null ? as : 'button'; if (typeof tag === 'string' && tag.toLowerCase() === 'button') return 'button'; return undefined; } function useResolveButtonType(data, refElement) { var type = ref(resolveType(data.value.type, data.value.as)); onMounted(function () { type.value = resolveType(data.value.type, data.value.as); }); watchEffect(function () { var _dom; if (type.value) return; if (!dom(refElement)) return; if (dom(refElement) instanceof HTMLButtonElement && !((_dom = dom(refElement)) == null ? void 0 : _dom.hasAttribute('type'))) { type.value = 'button'; } }); return type; } var DisclosureStates; (function (DisclosureStates) { DisclosureStates[DisclosureStates["Open"] = 0] = "Open"; DisclosureStates[DisclosureStates["Closed"] = 1] = "Closed"; })(DisclosureStates || (DisclosureStates = {})); var DisclosureContext = /*#__PURE__*/Symbol('DisclosureContext'); function useDisclosureContext(component) { var context = inject(DisclosureContext, null); if (context === null) { var err = new Error("<" + component + " /> is missing a parent <Disclosure /> component."); if (Error.captureStackTrace) Error.captureStackTrace(err, useDisclosureContext); throw err; } return context; } var DisclosurePanelContext = /*#__PURE__*/Symbol('DisclosurePanelContext'); function useDisclosurePanelContext() { return inject(DisclosurePanelContext, null); } // --- var Disclosure = /*#__PURE__*/defineComponent({ name: 'Disclosure', props: { as: { type: [Object, String], "default": 'template' }, defaultOpen: { type: [Boolean], "default": false } }, setup: function setup(props, _ref) { var slots = _ref.slots, attrs = _ref.attrs; var buttonId = "headlessui-disclosure-button-" + useId(); var panelId = "headlessui-disclosure-panel-" + useId(); var disclosureState = ref(props.defaultOpen ? DisclosureStates.Open : DisclosureStates.Closed); var panelRef = ref(null); var buttonRef = ref(null); var api = { buttonId: buttonId, panelId: panelId, disclosureState: disclosureState, panel: panelRef, button: buttonRef, toggleDisclosure: function toggleDisclosure() { var _match; disclosureState.value = match(disclosureState.value, (_match = {}, _match[DisclosureStates.Open] = DisclosureStates.Closed, _match[DisclosureStates.Closed] = DisclosureStates.Open, _match)); }, closeDisclosure: function closeDisclosure() { if (disclosureState.value === DisclosureStates.Closed) return; disclosureState.value = DisclosureStates.Closed; }, close: function close(focusableElement) { api.closeDisclosure(); var restoreElement = function () { if (!focusableElement) return dom(api.button); if (focusableElement instanceof HTMLElement) return focusableElement; if (focusableElement.value instanceof HTMLElement) return dom(focusableElement); return dom(api.button); }(); restoreElement == null ? void 0 : restoreElement.focus(); } }; provide(DisclosureContext, api); useOpenClosedProvider(computed(function () { var _match2; return match(disclosureState.value, (_match2 = {}, _match2[DisclosureStates.Open] = State.Open, _match2[DisclosureStates.Closed] = State.Closed, _match2)); })); return function () { var passThroughProps = _objectWithoutPropertiesLoose(props, ["defaultOpen"]); var slot = { open: disclosureState.value === DisclosureStates.Open, close: api.close }; return render({ props: passThroughProps, slot: slot, slots: slots, attrs: attrs, name: 'Disclosure' }); }; } }); // --- var DisclosureButton = /*#__PURE__*/defineComponent({ name: 'DisclosureButton', props: { as: { type: [Object, String], "default": 'button' }, disabled: { type: [Boolean], "default": false } }, render: function render$1() { var api = useDisclosureContext('DisclosureButton'); var slot = { open: api.disclosureState.value === DisclosureStates.Open }; var propsWeControl = this.isWithinPanel ? { ref: 'el', type: this.type, onClick: this.handleClick, onKeydown: this.handleKeyDown } : { id: this.id, ref: 'el', type: this.type, 'aria-expanded': this.$props.disabled ? undefined : api.disclosureState.value === DisclosureStates.Open, 'aria-controls': dom(api.panel) ? api.panelId : undefined, disabled: this.$props.disabled ? true : undefined, onClick: this.handleClick, onKeydown: this.handleKeyDown, onKeyup: this.handleKeyUp }; return render({ props: _extends({}, this.$props, propsWeControl), slot: slot, attrs: this.$attrs, slots: this.$slots, name: 'DisclosureButton' }); }, setup: function setup(props, _ref2) { var attrs = _ref2.attrs; var api = useDisclosureContext('DisclosureButton'); var panelContext = useDisclosurePanelContext(); var isWithinPanel = panelContext === null ? false : panelContext === api.panelId; var elementRef = ref(null); if (!isWithinPanel) { watchEffect(function () { api.button.value = elementRef.value; }); } return { isWithinPanel: isWithinPanel, id: api.buttonId, el: elementRef, type: useResolveButtonType(computed(function () { return { as: props.as, type: attrs.type }; }), elementRef), handleClick: function handleClick() { if (props.disabled) return; if (isWithinPanel) { var _dom; api.toggleDisclosure(); (_dom = dom(api.button)) == null ? void 0 : _dom.focus(); } else { api.toggleDisclosure(); } }, handleKeyDown: function handleKeyDown(event) { var _dom2; if (props.disabled) return; if (isWithinPanel) { switch (event.key) { case Keys.Space: case Keys.Enter: event.preventDefault(); event.stopPropagation(); api.toggleDisclosure(); (_dom2 = dom(api.button)) == null ? void 0 : _dom2.focus(); break; } } else { switch (event.key) { case Keys.Space: case Keys.Enter: event.preventDefault(); event.stopPropagation(); api.toggleDisclosure(); break; } } }, handleKeyUp: function handleKeyUp(event) { switch (event.key) { case Keys.Space: // Required for firefox, event.preventDefault() in handleKeyDown for // the Space key doesn't cancel the handleKeyUp, which in turn // triggers a *click*. event.preventDefault(); break; } } }; } }); // --- var DisclosurePanel = /*#__PURE__*/defineComponent({ name: 'DisclosurePanel', props: { as: { type: [Object, String], "default": 'div' }, "static": { type: Boolean, "default": false }, unmount: { type: Boolean, "default": true } }, render: function render$1() { var api = useDisclosureContext('DisclosurePanel'); var slot = { open: api.disclosureState.value === DisclosureStates.Open, close: api.close }; var propsWeControl = { id: this.id, ref: 'el' }; return render({ props: _extends({}, this.$props, propsWeControl), slot: slot, attrs: this.$attrs, slots: this.$slots, features: Features.RenderStrategy | Features.Static, visible: this.visible, name: 'DisclosurePanel' }); }, setup: function setup() { var api = useDisclosureContext('DisclosurePanel'); provide(DisclosurePanelContext, api.panelId); var usesOpenClosedState = useOpenClosed(); var visible = computed(function () { if (usesOpenClosedState !== null) { return usesOpenClosedState.value === State.Open; } return api.disclosureState.value === DisclosureStates.Open; }); return { id: api.panelId, el: api.panel, visible: visible }; } }); var FocusTrap = /*#__PURE__*/defineComponent({ name: 'FocusTrap', props: { as: { type: [Object, String], "default": 'div' }, initialFocus: { type: Object, "default": null } }, render: function render$1() { var slot = {}; var propsWeControl = { ref: 'el' }; var _this$$props = this.$props, passThroughProps = _objectWithoutPropertiesLoose(_this$$props, ["initialFocus"]); return render({ props: _extends({}, passThroughProps, propsWeControl), slot: slot, attrs: this.$attrs, slots: this.$slots, name: 'FocusTrap' }); }, setup: function setup(props) { var containers = ref(new Set()); var container = ref(null); var enabled = ref(true); var focusTrapOptions = computed(function () { return { initialFocus: props.initialFocus }; }); onMounted(function () { if (!container.value) return; containers.value.add(container.value); useFocusTrap(containers, enabled, focusTrapOptions); }); onUnmounted(function () { enabled.value = false; }); return { el: container }; } }); function assertNever(x) { throw new Error('Unexpected object: ' + x); } var Focus$1; (function (Focus) { /** Focus the first non-disabled item. */ Focus[Focus["First"] = 0] = "First"; /** Focus the previous non-disabled item. */ Focus[Focus["Previous"] = 1] = "Previous"; /** Focus the next non-disabled item. */ Focus[Focus["Next"] = 2] = "Next"; /** Focus the last non-disabled item. */ Focus[Focus["Last"] = 3] = "Last"; /** Focus a specific item based on the `id` of the item. */ Focus[Focus["Specific"] = 4] = "Specific"; /** Focus no items at all. */ Focus[Focus["Nothing"] = 5] = "Nothing"; })(Focus$1 || (Focus$1 = {})); function calculateActiveIndex(action, resolvers) { var items = resolvers.resolveItems(); if (items.length <= 0) return null; var currentActiveIndex = resolvers.resolveActiveIndex(); var activeIndex = currentActiveIndex != null ? currentActiveIndex : -1; var nextActiveIndex = function () { switch (action.focus) { case Focus$1.First: return items.findIndex(function (item) { return !resolvers.resolveDisabled(item); }); case Focus$1.Previous: { var idx = items.slice().reverse().findIndex(function (item, idx, all) { if (activeIndex !== -1 && all.length - idx - 1 >= activeIndex) return false; return !resolvers.resolveDisabled(item); }); if (idx === -1) return idx; return items.length - 1 - idx; } case Focus$1.Next: return items.findIndex(function (item, idx) { if (idx <= activeIndex) return false; return !resolvers.resolveDisabled(item); }); case Focus$1.Last: { var _idx = items.slice().reverse().findIndex(function (item) { return !resolvers.resolveDisabled(item); }); if (_idx === -1) return _idx; return items.length - 1 - _idx; } case Focus$1.Specific: return items.findIndex(function (item) { return resolvers.resolveId(item) === action.id; }); case Focus$1.Nothing: return null; default: assertNever(action); } }(); return nextActiveIndex === -1 ? currentActiveIndex : nextActiveIndex; } var ListboxStates; (function (ListboxStates) { ListboxStates[ListboxStates["Open"] = 0] = "Open"; ListboxStates[ListboxStates["Closed"] = 1] = "Closed"; })(ListboxStates || (ListboxStates = {})); function nextFrame(cb) { requestAnimationFrame(function () { return