@pienter/ui 0.3.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/CONVENTIONS.md +342 -385
  3. package/README.md +33 -18
  4. package/components/display/record-details/RecordDetails.vue +61 -0
  5. package/components/display/record-details/record-details.css +37 -0
  6. package/components/display/record-details/types.ts +8 -0
  7. package/components/feedback/toast/toast.css +1 -1
  8. package/components/form/block-editor/BlockEditor.vue +454 -0
  9. package/components/form/block-editor/block-editor.css +149 -0
  10. package/components/form/block-editor/types.ts +15 -0
  11. package/components/form/combobox/Combobox.vue +42 -72
  12. package/components/form/combobox/combobox.css +1 -1
  13. package/components/form/form/Form.vue +1 -2
  14. package/components/form/label/label.css +1 -1
  15. package/components/form/number-field/NumberField.vue +1 -2
  16. package/components/form/number-field/number-field.css +1 -1
  17. package/components/form/radio-group/RadioGroup.vue +2 -5
  18. package/components/form/record-form/RecordFields.vue +128 -0
  19. package/components/form/record-form/RecordForm.vue +116 -0
  20. package/components/form/record-form/fields.ts +20 -0
  21. package/components/form/record-form/record-form.css +15 -0
  22. package/components/form/record-form/types.ts +28 -0
  23. package/components/form/slider/slider.css +2 -3
  24. package/components/form/tags-input/tags-input.css +1 -2
  25. package/components/form/text-input/text-input.css +2 -0
  26. package/components/form/textarea/textarea.css +1 -1
  27. package/components/layout/accordion/Accordion.vue +6 -27
  28. package/components/layout/collapsible/Collapsible.vue +9 -19
  29. package/components/layout/index/Index.vue +353 -0
  30. package/components/layout/index/index.css +114 -0
  31. package/components/layout/index/useIndex.ts +390 -0
  32. package/components/layout/table/table.css +5 -4
  33. package/components/navigation/breadcrumb/Breadcrumb.vue +24 -5
  34. package/components/navigation/breadcrumb/breadcrumb.css +15 -0
  35. package/components/navigation/sidebar/Sidebar.vue +329 -84
  36. package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
  37. package/components/navigation/sidebar/context.ts +21 -0
  38. package/components/navigation/sidebar/sidebar.css +346 -78
  39. package/components/navigation/sidebar/types.ts +13 -1
  40. package/components/navigation/tabs/Tabs.vue +6 -0
  41. package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
  42. package/components/overlay/command/Command.vue +10 -38
  43. package/components/overlay/command/command.css +1 -1
  44. package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
  45. package/components/overlay/modal/Modal.vue +7 -28
  46. package/components/overlay/popover/Popover.vue +9 -31
  47. package/components/overlay/sheet/Sheet.vue +7 -28
  48. package/components/overlay/tooltip/Tooltip.vue +14 -19
  49. package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
  50. package/composables/useDisclosure.ts +113 -0
  51. package/composables/useEventListener.ts +16 -0
  52. package/composables/useMenu.ts +212 -0
  53. package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
  54. package/package.json +16 -40
  55. package/styles/0-settings/colors.css +10 -0
  56. package/styles/0-settings/layout.css +18 -0
  57. package/styles/0-settings/motion.css +2 -2
  58. package/styles/0-settings/spacing.css +3 -1
  59. package/utils/a11y/focus.ts +9 -3
  60. package/utils/cms/index.ts +283 -0
  61. package/utils/cms/schema.json +126 -0
  62. package/utils/navigation/sidebar.ts +97 -0
  63. package/utils/validation/form.ts +6 -9
  64. package/components/action/button/Button.astro +0 -95
  65. package/components/action/button/IconButton.astro +0 -86
  66. package/components/display/avatar/Avatar.astro +0 -17
  67. package/components/display/avatar/AvatarStack.astro +0 -9
  68. package/components/display/badge/Badge.astro +0 -15
  69. package/components/display/empty/Empty.astro +0 -9
  70. package/components/display/icon/Icon.astro +0 -52
  71. package/components/feedback/alert/Alert.astro +0 -52
  72. package/components/feedback/progress/Progress.astro +0 -68
  73. package/components/feedback/skeleton/Skeleton.astro +0 -32
  74. package/components/feedback/spinner/Spinner.astro +0 -25
  75. package/components/feedback/toast/Toast.astro +0 -50
  76. package/components/form/checkbox/Checkbox.astro +0 -79
  77. package/components/form/date-input/DateInput.astro +0 -105
  78. package/components/form/form/Form.astro +0 -106
  79. package/components/form/input-otp/InputOTP.astro +0 -147
  80. package/components/form/label/Label.astro +0 -13
  81. package/components/form/number-field/NumberField.astro +0 -142
  82. package/components/form/radio-group/RadioGroup.astro +0 -105
  83. package/components/form/select/Select.astro +0 -105
  84. package/components/form/slider/Slider.astro +0 -205
  85. package/components/form/switch/Switch.astro +0 -75
  86. package/components/form/tags-input/TagsInput.astro +0 -153
  87. package/components/form/text-input/TextInput.astro +0 -84
  88. package/components/form/textarea/Textarea.astro +0 -86
  89. package/components/layout/card/Card.astro +0 -13
  90. package/components/layout/separator/Separator.astro +0 -31
  91. package/components/layout/table/Table.astro +0 -116
  92. package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
  93. package/components/navigation/navbar/Navbar.astro +0 -62
  94. package/components/navigation/pagination/PaginationFooter.astro +0 -24
  95. package/components/navigation/sidebar/Sidebar.astro +0 -132
  96. package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
  97. package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
  98. package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
  99. package/components/overlay/modal/Modal.astro +0 -66
  100. package/utils/controllers/disclosure.ts +0 -117
  101. package/utils/controllers/form.ts +0 -524
  102. package/utils/controllers/index.ts +0 -39
  103. package/utils/controllers/menu.ts +0 -255
  104. package/utils/controllers/number-field.ts +0 -103
  105. package/utils/controllers/otp.ts +0 -252
  106. package/utils/controllers/sidebar.ts +0 -610
  107. package/utils/controllers/slider.ts +0 -336
  108. package/utils/controllers/tags-input.ts +0 -255
  109. package/utils/controllers/toast.ts +0 -426
@@ -38,12 +38,9 @@
38
38
  </template>
39
39
 
40
40
  <script setup lang="ts">
41
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
41
+ import { ref } from 'vue';
42
42
  import { generateId } from '../../../utils/a11y/id.js';
43
- import {
44
- mountDialog,
45
- type DialogControl,
46
- } from '../../../utils/controllers/dialog.js';
43
+ import { useDialog } from '../../../composables/useDialog.js';
47
44
 
48
45
  defineOptions({ inheritAttrs: false });
49
46
 
@@ -72,7 +69,6 @@ const emit = defineEmits<{
72
69
  const rootRef = ref<HTMLDialogElement | null>(null);
73
70
  const titleId = generateId('alertdialog-title');
74
71
  const descriptionId = generateId('alertdialog-desc');
75
- let control: DialogControl | null = null;
76
72
 
77
73
  function onConfirm(): void {
78
74
  emit('confirm');
@@ -84,31 +80,14 @@ function onCancel(): void {
84
80
  emit('update:open', false);
85
81
  }
86
82
 
87
- onMounted(() => {
88
- if (!rootRef.value) return;
89
- control = mountDialog(rootRef.value, {
90
- role: 'alertdialog',
91
- persistent: true,
92
- initialFocus: 'cancel',
93
- // No onClose persistent dialogs suppress Escape and backdrop-click
94
- // dismissal at the controller layer. Close happens only through the
95
- // explicit confirm/cancel buttons wired in the template.
96
- });
97
- if (props.open) control.open();
98
- });
99
-
100
- watch(
101
- () => props.open,
102
- (isOpen) => {
103
- if (!control) return;
104
- if (isOpen) control.open();
105
- else control.close();
106
- },
107
- );
108
-
109
- onBeforeUnmount(() => {
110
- control?.teardown();
111
- control = null;
83
+ useDialog(rootRef, {
84
+ role: 'alertdialog',
85
+ persistent: true,
86
+ initialFocus: 'cancel',
87
+ open: () => props.open,
88
+ // No onClose — persistent dialogs suppress Escape and backdrop-click
89
+ // dismissal at the composable layer. Close happens only through the
90
+ // explicit confirm/cancel buttons wired in the template.
112
91
  });
113
92
  </script>
114
93
 
@@ -82,11 +82,8 @@
82
82
  </template>
83
83
 
84
84
  <script setup lang="ts">
85
- import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue';
86
- import {
87
- mountDialog,
88
- type DialogControl,
89
- } from '../../../utils/controllers/dialog.js';
85
+ import { computed, ref } from 'vue';
86
+ import { useDialog } from '../../../composables/useDialog.js';
90
87
  import { generateId } from '../../../utils/a11y/id.js';
91
88
  import Icon from '../../display/icon/Icon.vue';
92
89
  import type { IconName } from '../../../icons/index.js';
@@ -164,8 +161,6 @@ const listboxRef = ref<HTMLUListElement | null>(null);
164
161
  const query = ref('');
165
162
  const activeIndex = ref<number | null>(null);
166
163
 
167
- let control: DialogControl | null = null;
168
-
169
164
  // `commandsOpen` mirrors the parent's `open` for the input's
170
165
  // `aria-expanded` state — when the dialog is open, the listbox
171
166
  // inside is also "open" (visible), so the combobox is expanded.
@@ -317,37 +312,14 @@ function resetState(): void {
317
312
  activeIndex.value = firstEnabled >= 0 ? firstEnabled : null;
318
313
  }
319
314
 
320
- onMounted(() => {
321
- if (!dialogRef.value) return;
322
- control = mountDialog(dialogRef.value, {
323
- role: 'dialog',
324
- onClose: requestClose,
325
- });
326
- if (props.open) {
327
- resetState();
328
- control.open();
329
- }
330
- });
331
-
332
- watch(
333
- () => props.open,
334
- (isOpen) => {
335
- if (!control) return;
336
- if (isOpen) {
337
- // Reset state on every open so the palette always starts
338
- // fresh — the canonical Cmd+K UX. Prior query / cursor
339
- // position are not preserved between sessions.
340
- resetState();
341
- control.open();
342
- } else {
343
- control.close();
344
- }
345
- },
346
- );
347
-
348
- onBeforeUnmount(() => {
349
- control?.teardown();
350
- control = null;
315
+ useDialog(dialogRef, {
316
+ role: 'dialog',
317
+ open: () => props.open,
318
+ // Reset state on every open so the palette always starts fresh —
319
+ // the canonical Cmd+K UX. Prior query / cursor position are not
320
+ // preserved between sessions.
321
+ onBeforeOpen: resetState,
322
+ onClose: requestClose,
351
323
  });
352
324
  </script>
353
325
 
@@ -2,7 +2,7 @@
2
2
  /* ===== pui-command =====
3
3
  *
4
4
  * Search palette built on the native `<dialog>` element via
5
- * `mountDialog`. Reuses the combobox-pattern input + listbox
5
+ * `useDialog`. Reuses the combobox-pattern input + listbox
6
6
  * inside the dialog chrome (search field at the top, scrollable
7
7
  * commands below). The dialog provides top-layer rendering, focus
8
8
  * trapping, and Escape dismissal; the controller layers on
@@ -13,13 +13,9 @@
13
13
  </template>
14
14
 
15
15
  <script setup lang="ts">
16
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
17
- import {
18
- mountPopover,
19
- type PopoverControl,
20
- type Placement,
21
- } from '../../../utils/controllers/popover.js';
22
- import { mountMenu } from '../../../utils/controllers/menu.js';
16
+ import { ref } from 'vue';
17
+ import { usePopover, type Placement } from '../../../composables/usePopover.js';
18
+ import { useMenu } from '../../../composables/useMenu.js';
23
19
 
24
20
  defineOptions({ inheritAttrs: false });
25
21
 
@@ -28,7 +24,7 @@ const props = withDefaults(
28
24
  /**
29
25
  * Required `id` for the dropdown menu element. The consumer
30
26
  * wires their trigger button via the native HTML
31
- * `popovertarget="<id>"` attribute, which `mountPopover`
27
+ * `popovertarget="<id>"` attribute, which `usePopover`
32
28
  * auto-detects on mount as the positioning anchor and as the
33
29
  * focus-restore target on close.
34
30
  */
@@ -78,63 +74,42 @@ const emit = defineEmits<{
78
74
  }>();
79
75
 
80
76
  const menuRef = ref<HTMLElement | null>(null);
81
- let popoverControl: PopoverControl | null = null;
82
- let menuTeardown: (() => void) | null = null;
83
77
 
84
- onMounted(() => {
85
- if (!menuRef.value) return;
86
- popoverControl = mountPopover(menuRef.value, {
87
- placement: props.placement,
88
- offset: props.offset,
89
- // `modal: false` (default) `popover="auto"` so the browser
90
- // handles outside-click / focus-loss / Escape light-dismiss.
91
- // The menu is non-modal — page content remains visible and
92
- // interactive once the menu closes.
93
- onOpen: () => {
94
- emit('update:open', true);
95
- // Move focus to the first menu item on open per the
96
- // WAI-ARIA Menu pattern. The browser does NOT auto-focus
97
- // popover content; the controller leaves focus management
98
- // to consumers, so we explicitly walk into the menu here.
99
- const firstItem = menuRef.value?.querySelector<HTMLElement>(
100
- '[role="menuitem"]:not([disabled]):not([aria-disabled="true"]),' +
101
- ' [role="menuitemcheckbox"]:not([disabled]):not([aria-disabled="true"]),' +
102
- ' [role="menuitemradio"]:not([disabled]):not([aria-disabled="true"])',
103
- );
104
- firstItem?.focus();
105
- },
106
- onClose: () => emit('update:open', false),
107
- });
108
- menuTeardown = mountMenu({
109
- menuEl: menuRef.value,
110
- // Escape closes the menu and lets `popover="auto"`'s
111
- // light-dismiss + the controller's `toggle` event handle
112
- // focus restoration to the trigger.
113
- onEscape: () => popoverControl?.hide(),
114
- // Auto-close on item click is the standard menu UX. The
115
- // consumer's own `@click` on each `<button role="menuitem">`
116
- // runs first (DOM bubble); this callback fires from
117
- // `mountMenu`'s container-level click listener and closes
118
- // the menu after the action.
119
- onItemActivate: () => popoverControl?.hide(),
120
- });
121
- if (props.open) popoverControl.show();
122
- });
123
-
124
- watch(
125
- () => props.open,
126
- (isOpen) => {
127
- if (!popoverControl || isOpen === undefined) return;
128
- if (isOpen) popoverControl.show();
129
- else popoverControl.hide();
78
+ const popover = usePopover(menuRef, {
79
+ placement: () => props.placement,
80
+ offset: () => props.offset,
81
+ // `modal: false` (default) → `popover="auto"` so the browser
82
+ // handles outside-click / focus-loss / Escape light-dismiss.
83
+ // The menu is non-modal page content remains visible and
84
+ // interactive once the menu closes.
85
+ open: () => props.open,
86
+ onOpen: () => {
87
+ emit('update:open', true);
88
+ // Move focus to the first menu item on open per the
89
+ // WAI-ARIA Menu pattern. The browser does NOT auto-focus
90
+ // popover content; the composable leaves focus management
91
+ // to consumers, so we explicitly walk into the menu here.
92
+ const firstItem = menuRef.value?.querySelector<HTMLElement>(
93
+ '[role="menuitem"]:not([disabled]):not([aria-disabled="true"]),' +
94
+ ' [role="menuitemcheckbox"]:not([disabled]):not([aria-disabled="true"]),' +
95
+ ' [role="menuitemradio"]:not([disabled]):not([aria-disabled="true"])',
96
+ );
97
+ firstItem?.focus();
130
98
  },
131
- );
99
+ onClose: () => emit('update:open', false),
100
+ });
132
101
 
133
- onBeforeUnmount(() => {
134
- menuTeardown?.();
135
- menuTeardown = null;
136
- popoverControl?.teardown();
137
- popoverControl = null;
102
+ useMenu(menuRef, {
103
+ // Escape closes the menu and lets `popover="auto"`'s
104
+ // light-dismiss + the composable's `toggle` event handle
105
+ // focus restoration to the trigger.
106
+ onEscape: () => popover.hide(),
107
+ // Auto-close on item click is the standard menu UX. The
108
+ // consumer's own `@click` on each `<button role="menuitem">`
109
+ // runs first (DOM bubble); this callback fires from
110
+ // `useMenu`'s container-level click listener and closes
111
+ // the menu after the action.
112
+ onItemActivate: () => popover.hide(),
138
113
  });
139
114
  </script>
140
115
 
@@ -19,12 +19,9 @@
19
19
  </template>
20
20
 
21
21
  <script setup lang="ts">
22
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
22
+ import { ref } from 'vue';
23
23
  import { generateId } from '../../../utils/a11y/id.js';
24
- import {
25
- mountDialog,
26
- type DialogControl,
27
- } from '../../../utils/controllers/dialog.js';
24
+ import { useDialog } from '../../../composables/useDialog.js';
28
25
 
29
26
  defineOptions({ inheritAttrs: false });
30
27
 
@@ -48,35 +45,17 @@ const emit = defineEmits<{
48
45
 
49
46
  const rootRef = ref<HTMLDialogElement | null>(null);
50
47
  const titleId = generateId('modal-title');
51
- let control: DialogControl | null = null;
52
48
 
53
49
  function requestClose() {
54
50
  emit('update:open', false);
55
51
  emit('close');
56
52
  }
57
53
 
58
- onMounted(() => {
59
- if (!rootRef.value) return;
60
- control = mountDialog(rootRef.value, {
61
- role: 'dialog',
62
- persistent: props.persistent,
63
- onClose: requestClose,
64
- });
65
- if (props.open) control.open();
66
- });
67
-
68
- watch(
69
- () => props.open,
70
- (isOpen) => {
71
- if (!control) return;
72
- if (isOpen) control.open();
73
- else control.close();
74
- },
75
- );
76
-
77
- onBeforeUnmount(() => {
78
- control?.teardown();
79
- control = null;
54
+ useDialog(rootRef, {
55
+ role: 'dialog',
56
+ persistent: () => props.persistent,
57
+ open: () => props.open,
58
+ onClose: requestClose,
80
59
  });
81
60
  </script>
82
61
 
@@ -14,12 +14,8 @@
14
14
  </template>
15
15
 
16
16
  <script setup lang="ts">
17
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
18
- import {
19
- mountPopover,
20
- type PopoverControl,
21
- type Placement,
22
- } from '../../../utils/controllers/popover.js';
17
+ import { ref } from 'vue';
18
+ import { usePopover, type Placement } from '../../../composables/usePopover.js';
23
19
 
24
20
  defineOptions({ inheritAttrs: false });
25
21
 
@@ -79,32 +75,14 @@ const emit = defineEmits<{
79
75
  }>();
80
76
 
81
77
  const rootRef = ref<HTMLElement | null>(null);
82
- let control: PopoverControl | null = null;
83
78
 
84
- onMounted(() => {
85
- if (!rootRef.value) return;
86
- control = mountPopover(rootRef.value, {
87
- placement: props.placement,
88
- offset: props.offset,
89
- modal: props.modal,
90
- onOpen: () => emit('update:open', true),
91
- onClose: () => emit('update:open', false),
92
- });
93
- if (props.open) control.show();
94
- });
95
-
96
- watch(
97
- () => props.open,
98
- (isOpen) => {
99
- if (!control || isOpen === undefined) return;
100
- if (isOpen) control.show();
101
- else control.hide();
102
- },
103
- );
104
-
105
- onBeforeUnmount(() => {
106
- control?.teardown();
107
- control = null;
79
+ usePopover(rootRef, {
80
+ placement: () => props.placement,
81
+ offset: () => props.offset,
82
+ modal: props.modal,
83
+ open: () => props.open,
84
+ onOpen: () => emit('update:open', true),
85
+ onClose: () => emit('update:open', false),
108
86
  });
109
87
  </script>
110
88
 
@@ -20,12 +20,9 @@
20
20
  </template>
21
21
 
22
22
  <script setup lang="ts">
23
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
23
+ import { ref } from 'vue';
24
24
  import { generateId } from '../../../utils/a11y/id.js';
25
- import {
26
- mountDialog,
27
- type DialogControl,
28
- } from '../../../utils/controllers/dialog.js';
25
+ import { useDialog } from '../../../composables/useDialog.js';
29
26
 
30
27
  defineOptions({ inheritAttrs: false });
31
28
 
@@ -51,35 +48,17 @@ const emit = defineEmits<{
51
48
 
52
49
  const rootRef = ref<HTMLDialogElement | null>(null);
53
50
  const titleId = generateId('sheet-title');
54
- let control: DialogControl | null = null;
55
51
 
56
52
  function requestClose(): void {
57
53
  emit('update:open', false);
58
54
  emit('close');
59
55
  }
60
56
 
61
- onMounted(() => {
62
- if (!rootRef.value) return;
63
- control = mountDialog(rootRef.value, {
64
- role: 'dialog',
65
- persistent: props.persistent,
66
- onClose: requestClose,
67
- });
68
- if (props.open) control.open();
69
- });
70
-
71
- watch(
72
- () => props.open,
73
- (isOpen) => {
74
- if (!control) return;
75
- if (isOpen) control.open();
76
- else control.close();
77
- },
78
- );
79
-
80
- onBeforeUnmount(() => {
81
- control?.teardown();
82
- control = null;
57
+ useDialog(rootRef, {
58
+ role: 'dialog',
59
+ persistent: () => props.persistent,
60
+ open: () => props.open,
61
+ onClose: requestClose,
83
62
  });
84
63
  </script>
85
64
 
@@ -13,11 +13,7 @@
13
13
 
14
14
  <script setup lang="ts">
15
15
  import { ref, onMounted, onBeforeUnmount } from 'vue';
16
- import {
17
- mountPopover,
18
- type PopoverControl,
19
- type Placement,
20
- } from '../../../utils/controllers/popover.js';
16
+ import { usePopover, type Placement } from '../../../composables/usePopover.js';
21
17
  import { generateId } from '../../../utils/a11y/id.js';
22
18
 
23
19
  const props = withDefaults(
@@ -64,11 +60,19 @@ const tooltipRef = ref<HTMLElement | null>(null);
64
60
  // `generateId` runs at script-setup execution time — safe in SSR/hydration
65
61
  // because the same id is produced in the same order on each render path.
66
62
  const tooltipId = generateId('tooltip');
67
- let control: PopoverControl | null = null;
68
63
  let openTimer: number | null = null;
69
64
  let closeTimer: number | null = null;
70
65
  let trigger: HTMLElement | null = null;
71
66
 
67
+ // The trigger is resolved from the slot in `onMounted`, after the
68
+ // composable wires itself; the lazy getter hands it over on first open.
69
+ const control = usePopover(tooltipRef, {
70
+ anchor: () => trigger,
71
+ placement: () => props.placement,
72
+ offset: 6,
73
+ modal: false,
74
+ });
75
+
72
76
  function clearTimers(): void {
73
77
  if (openTimer != null) {
74
78
  clearTimeout(openTimer);
@@ -83,7 +87,7 @@ function clearTimers(): void {
83
87
  function scheduleOpen(): void {
84
88
  clearTimers();
85
89
  openTimer = window.setTimeout(() => {
86
- control?.show();
90
+ control.show();
87
91
  openTimer = null;
88
92
  }, props.openDelay);
89
93
  }
@@ -91,7 +95,7 @@ function scheduleOpen(): void {
91
95
  function scheduleClose(): void {
92
96
  clearTimers();
93
97
  closeTimer = window.setTimeout(() => {
94
- control?.hide();
98
+ control.hide();
95
99
  closeTimer = null;
96
100
  }, props.closeDelay);
97
101
  }
@@ -110,12 +114,12 @@ function onPointerLeave(): void {
110
114
  // are a mouse-only affordance.
111
115
  function onFocus(): void {
112
116
  clearTimers();
113
- control?.show();
117
+ control.show();
114
118
  }
115
119
 
116
120
  function onBlur(): void {
117
121
  clearTimers();
118
- control?.hide();
122
+ control.hide();
119
123
  }
120
124
 
121
125
  function onTooltipPointerEnter(): void {
@@ -154,13 +158,6 @@ onMounted(() => {
154
158
  existing ? `${existing} ${tooltipId}` : tooltipId,
155
159
  );
156
160
 
157
- control = mountPopover(tooltipEl, {
158
- anchor: trigger,
159
- placement: props.placement,
160
- offset: 6,
161
- modal: false,
162
- });
163
-
164
161
  trigger.addEventListener('pointerenter', onPointerEnter);
165
162
  trigger.addEventListener('pointerleave', onPointerLeave);
166
163
  trigger.addEventListener('focus', onFocus);
@@ -199,8 +196,6 @@ onBeforeUnmount(() => {
199
196
  onTooltipPointerLeave,
200
197
  );
201
198
  }
202
- control?.teardown();
203
- control = null;
204
199
  trigger = null;
205
200
  });
206
201
  </script>