@destyler/popper 0.0.3 → 0.0.5

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.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @destyler/popper
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/@destyler/popper?color=a1b858&popper=)](https://www.npmjs.com/package/@destyler/popper)
4
+
5
+ > popper is a component that allows you to create popups, tooltips, and dropdowns.
6
+
7
+ [GitHub](https://github.com/destyler/destyler) | [Documentation](https://destyler-dev.zeabur.app/)
@@ -1,16 +1,460 @@
1
1
  'use strict';
2
2
 
3
- const index = require('./index.cjs');
4
- require('vue');
5
- require('@destyler/shared');
6
- require('@destyler/primitive');
7
- require('@destyler/composition');
8
- require('@floating-ui/vue');
9
- require('@destyler/arrow');
3
+ const vue = require('vue');
4
+ const shared = require('@destyler/shared');
5
+ const primitive = require('@destyler/primitive');
6
+ const composition = require('@destyler/composition');
7
+ const vue$1 = require('@floating-ui/vue');
8
+ const arrow = require('@destyler/arrow');
9
+ const component = require('@destyler/arrow/component');
10
10
 
11
+ const [injectPopperRootContext, providePopperRootContext] = shared.createContext("DestylerPopperRoot");
12
+ const PopperRoot = vue.defineComponent({
13
+ name: "PopperRoot",
14
+ setup() {
15
+ const anchor = vue.ref();
16
+ providePopperRootContext({
17
+ anchor,
18
+ onAnchorChange: (element) => anchor.value = element
19
+ });
20
+ },
21
+ render() {
22
+ return this.$slots.default?.();
23
+ }
24
+ });
11
25
 
26
+ const popperAnchorProps = {
27
+ ...primitive.primitiveProps,
28
+ element: {
29
+ type: Object,
30
+ required: false
31
+ }
32
+ };
33
+ const PopperAnchor = vue.defineComponent({
34
+ name: "DestylerPopperAnchor",
35
+ props: popperAnchorProps,
36
+ setup(props) {
37
+ const { forwardRef, currentElement } = composition.useForwardExpose();
38
+ const rootContext = injectPopperRootContext();
39
+ vue.watch(currentElement, () => {
40
+ rootContext.onAnchorChange(props.element ?? currentElement.value);
41
+ });
42
+ return {
43
+ forwardRef
44
+ };
45
+ },
46
+ render() {
47
+ return vue.h(primitive.Primitive, {
48
+ as: this.$props.as,
49
+ asChild: this.$props.asChild,
50
+ ref: (el) => this.forwardRef(el)
51
+ }, () => this.$slots.default?.());
52
+ }
53
+ });
12
54
 
13
- exports.DestylerPopperAnchor = index.DestylerPopperAnchor;
14
- exports.DestylerPopperArrow = index.DestylerPopperArrow;
15
- exports.DestylerPopperContent = index.DestylerPopperContent;
16
- exports.DestylerPopperRoot = index.DestylerPopperRoot;
55
+ function isNotNull(value) {
56
+ return value !== null;
57
+ }
58
+ function transformOrigin(options) {
59
+ return {
60
+ name: "transformOrigin",
61
+ options,
62
+ fn(data) {
63
+ const { placement, rects, middlewareData } = data;
64
+ const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
65
+ const isArrowHidden = cannotCenterArrow;
66
+ const arrowWidth = isArrowHidden ? 0 : options.arrowWidth;
67
+ const arrowHeight = isArrowHidden ? 0 : options.arrowHeight;
68
+ const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
69
+ const noArrowAlign = { start: "0%", center: "50%", end: "100%" }[placedAlign];
70
+ const arrowXCenter = (middlewareData.arrow?.x ?? 0) + arrowWidth / 2;
71
+ const arrowYCenter = (middlewareData.arrow?.y ?? 0) + arrowHeight / 2;
72
+ let x = "";
73
+ let y = "";
74
+ if (placedSide === "bottom") {
75
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
76
+ y = `${-arrowHeight}px`;
77
+ } else if (placedSide === "top") {
78
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
79
+ y = `${rects.floating.height + arrowHeight}px`;
80
+ } else if (placedSide === "right") {
81
+ x = `${-arrowHeight}px`;
82
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
83
+ } else if (placedSide === "left") {
84
+ x = `${rects.floating.width + arrowHeight}px`;
85
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
86
+ }
87
+ return { data: { x, y } };
88
+ }
89
+ };
90
+ }
91
+ function getSideAndAlignFromPlacement(placement) {
92
+ const [side, align = "center"] = placement.split("-");
93
+ return [side, align];
94
+ }
95
+
96
+ const [injectPopperContentContext, providePopperContentContext] = shared.createContext("DestylerPopperContent");
97
+ const popperContentProps = {
98
+ ...primitive.primitiveProps,
99
+ /**
100
+ * The preferred side of the trigger to render against when open.
101
+ * Will be reversed when collisions occur and avoidCollisions
102
+ * is enabled.
103
+ *
104
+ * @default bottom
105
+ */
106
+ side: {
107
+ type: String,
108
+ required: false,
109
+ default: "bottom"
110
+ },
111
+ /**
112
+ * The distance in pixels from the trigger.
113
+ *
114
+ * @default 0
115
+ */
116
+ sideOffset: {
117
+ type: Number,
118
+ required: false,
119
+ default: 0
120
+ },
121
+ /**
122
+ * The preferred alignment against the trigger.
123
+ * May change when collisions occur.
124
+ *
125
+ * @default center
126
+ */
127
+ align: {
128
+ type: String,
129
+ required: false,
130
+ default: "center"
131
+ },
132
+ /**
133
+ * An offset in pixels from the `start` or `end` alignment options.
134
+ *
135
+ * @default 0
136
+ */
137
+ alignOffset: {
138
+ type: Number,
139
+ required: false,
140
+ default: 0
141
+ },
142
+ /**
143
+ * When `true`, overrides the side andalign preferences
144
+ * to prevent collisions with boundary edges.
145
+ *
146
+ * @default true
147
+ */
148
+ avoidCollisions: {
149
+ type: Boolean,
150
+ required: false,
151
+ default: true
152
+ },
153
+ /**
154
+ * The element used as the collision boundary. By default
155
+ * this is the viewport, though you can provide additional
156
+ * element(s) to be included in this check.
157
+ *
158
+ * @default () => []
159
+ */
160
+ collisionBoundary: {
161
+ type: [Object, Array, null],
162
+ required: false,
163
+ default: () => []
164
+ },
165
+ /**
166
+ * The distance in pixels from the boundary edges where collision
167
+ * detection should occur. Accepts a number (same for all sides),
168
+ * or a partial padding object, for example: { top: 20, left: 20 }.
169
+ *
170
+ * @default 0
171
+ */
172
+ collisionPadding: {
173
+ type: [Number, Object],
174
+ required: false,
175
+ default: 0
176
+ },
177
+ /**
178
+ * The distance in pixels from the boundary edges where collision
179
+ * detection should occur. Accepts a number (same for all sides),
180
+ * or a partial padding object, for example: { top: 20, left: 20 }.
181
+ *
182
+ * @default 0
183
+ */
184
+ arrowPadding: {
185
+ type: Number,
186
+ required: false,
187
+ default: 0
188
+ },
189
+ /**
190
+ * The sticky behavior on the align axis. `partial` will keep the
191
+ * content in the boundary as long as the trigger is at least partially
192
+ * in the boundary whilst "always" will keep the content in the boundary
193
+ * regardless.
194
+ *
195
+ * @default partial
196
+ */
197
+ sticky: {
198
+ type: String,
199
+ required: false,
200
+ default: "partial"
201
+ },
202
+ /**
203
+ * Whether to hide the content when the trigger becomes fully occluded.
204
+ *
205
+ * @default false
206
+ */
207
+ hideWhenDetached: {
208
+ type: Boolean,
209
+ required: false,
210
+ default: false
211
+ },
212
+ /**
213
+ * Strategy to update the position of the floating element on every animation frame.
214
+ *
215
+ * @default optimized
216
+ */
217
+ updatePositionStrategy: {
218
+ type: String,
219
+ required: false,
220
+ default: "optimized"
221
+ },
222
+ /**
223
+ * Force content to be position within the viewport.
224
+ *
225
+ * Might overlap the reference element, which may not be desired.
226
+ *
227
+ * @default false
228
+ */
229
+ prioritizePosition: {
230
+ type: Boolean,
231
+ required: false,
232
+ default: false
233
+ }
234
+ };
235
+ const popperContentEmits = {
236
+ placed: () => true
237
+ };
238
+ const PopperContent = vue.defineComponent({
239
+ name: "DestylerPopperContent",
240
+ inheritAttrs: false,
241
+ props: popperContentProps,
242
+ emits: popperContentEmits,
243
+ setup(props, { attrs, emit }) {
244
+ const rootContext = injectPopperRootContext();
245
+ const { forwardRef, currentElement: contentElement } = composition.useForwardExpose();
246
+ const floatingRef = vue.ref();
247
+ const arrow = vue.ref();
248
+ const { width: arrowWidth, height: arrowHeight } = composition.useSize(arrow);
249
+ const desiredPlacement = vue.computed(() => props.side + (props.align !== "center" ? `-${props.align}` : ""));
250
+ const collisionPadding = vue.computed(() => {
251
+ return typeof props.collisionPadding === "number" ? props.collisionPadding : { top: 0, right: 0, bottom: 0, left: 0, ...props.collisionPadding };
252
+ });
253
+ const boundary = vue.computed(() => {
254
+ return Array.isArray(props.collisionBoundary) ? props.collisionBoundary : [props.collisionBoundary];
255
+ });
256
+ const detectOverflowOptions = vue.computed(() => {
257
+ return {
258
+ padding: collisionPadding.value,
259
+ boundary: boundary.value.filter(isNotNull),
260
+ // with `strategy: 'fixed'`, this is the only way to get it to respect boundaries
261
+ altBoundary: boundary.value.length > 0
262
+ };
263
+ });
264
+ const computedMiddleware = shared.computedEager(() => {
265
+ return [
266
+ vue$1.offset({
267
+ mainAxis: props.sideOffset + arrowHeight.value,
268
+ alignmentAxis: props.alignOffset
269
+ }),
270
+ props.avoidCollisions && vue$1.shift({
271
+ mainAxis: true,
272
+ crossAxis: !!props.prioritizePosition,
273
+ limiter: props.sticky === "partial" ? vue$1.limitShift() : void 0,
274
+ ...detectOverflowOptions.value
275
+ }),
276
+ !props.prioritizePosition && props.avoidCollisions && vue$1.flip({
277
+ ...detectOverflowOptions.value
278
+ }),
279
+ vue$1.size({
280
+ ...detectOverflowOptions.value,
281
+ apply: ({ elements, rects, availableWidth, availableHeight }) => {
282
+ const { width: anchorWidth, height: anchorHeight } = rects.reference;
283
+ const contentStyle = elements.floating.style;
284
+ Object.assign(elements.floating.style, {
285
+ maxWidth: `${availableWidth}px`,
286
+ maxHeight: `${availableHeight}px`
287
+ });
288
+ contentStyle.setProperty(
289
+ "--destyler-popper-available-width",
290
+ `${availableWidth}px`
291
+ );
292
+ contentStyle.setProperty(
293
+ "--destyler-popper-available-height",
294
+ `${availableHeight}px`
295
+ );
296
+ contentStyle.setProperty(
297
+ "--destyler-popper-anchor-width",
298
+ `${anchorWidth}px`
299
+ );
300
+ contentStyle.setProperty(
301
+ "--destyler-popper-anchor-height",
302
+ `${anchorHeight}px`
303
+ );
304
+ }
305
+ }),
306
+ arrow.value && vue$1.arrow({ element: arrow.value, padding: props.arrowPadding }),
307
+ transformOrigin({
308
+ arrowWidth: arrowWidth.value,
309
+ arrowHeight: arrowHeight.value
310
+ }),
311
+ props.hideWhenDetached && vue$1.hide({ strategy: "referenceHidden", ...detectOverflowOptions.value })
312
+ ];
313
+ });
314
+ const { floatingStyles, placement, isPositioned, middlewareData } = vue$1.useFloating(
315
+ rootContext.anchor,
316
+ floatingRef,
317
+ {
318
+ strategy: "fixed",
319
+ placement: desiredPlacement,
320
+ whileElementsMounted: (...args) => {
321
+ const cleanup = vue$1.autoUpdate(...args, {
322
+ animationFrame: props.updatePositionStrategy === "always"
323
+ });
324
+ return cleanup;
325
+ },
326
+ middleware: computedMiddleware
327
+ }
328
+ );
329
+ const placedSide = vue.computed(() => getSideAndAlignFromPlacement(placement.value)[0]);
330
+ const placedAlign = vue.computed(() => getSideAndAlignFromPlacement(placement.value)[1]);
331
+ vue.watchEffect(() => {
332
+ if (isPositioned.value)
333
+ emit("placed");
334
+ });
335
+ const cannotCenterArrow = vue.computed(
336
+ () => middlewareData.value.arrow?.centerOffset !== 0
337
+ );
338
+ const contentZIndex = vue.ref("");
339
+ vue.watchEffect(() => {
340
+ if (contentElement.value)
341
+ contentZIndex.value = window.getComputedStyle(contentElement.value).zIndex;
342
+ });
343
+ const arrowX = vue.computed(() => middlewareData.value.arrow?.x ?? 0);
344
+ const arrowY = vue.computed(() => middlewareData.value.arrow?.y ?? 0);
345
+ providePopperContentContext({
346
+ placedSide,
347
+ onArrowChange: (element) => arrow.value = element,
348
+ arrowX,
349
+ arrowY,
350
+ shouldHideArrow: cannotCenterArrow
351
+ });
352
+ return {
353
+ floatingRef,
354
+ floatingStyles,
355
+ isPositioned,
356
+ contentZIndex,
357
+ middlewareData,
358
+ forwardRef,
359
+ placedSide,
360
+ placedAlign,
361
+ attrStyle: attrs.style
362
+ };
363
+ },
364
+ render() {
365
+ return vue.h("div", {
366
+ "data-destyler-popper-content-wrapper": "",
367
+ "ref": "floatingRef",
368
+ "style": {
369
+ ...this.floatingStyles,
370
+ transform: this.isPositioned ? this.floatingStyles.transform : "translate(0, -200%)",
371
+ minWidth: "max-content",
372
+ zIndex: this.contentZIndex,
373
+ ["--destyler-popper-transform-origin"]: [
374
+ this.middlewareData.transformOrigin?.x,
375
+ this.middlewareData.transformOrigin?.y
376
+ ].join(" "),
377
+ ...this.middlewareData.hide?.referenceHidden && {
378
+ visibility: "hidden",
379
+ pointerEvents: "none"
380
+ }
381
+ }
382
+ }, vue.h(primitive.Primitive, vue.mergeProps(this.$attrs, {
383
+ "ref": (el) => this.forwardRef(el),
384
+ "as": this.$props.as,
385
+ "asChild": this.$props.asChild,
386
+ "data-side": this.placedSide,
387
+ "data-align": this.placedAlign,
388
+ "style": {
389
+ animation: !this.isPositioned ? "none" : void 0
390
+ }
391
+ }), () => this.$slots.default?.()));
392
+ }
393
+ });
394
+
395
+ const OPPOSITE_SIDE = {
396
+ top: "bottom",
397
+ right: "left",
398
+ bottom: "top",
399
+ left: "right"
400
+ };
401
+ const popperArrowProps = {
402
+ ...component.arrowProps
403
+ };
404
+ const PopperArrow = vue.defineComponent({
405
+ name: "DestylerPopperArrow",
406
+ inheritAttrs: false,
407
+ props: popperArrowProps,
408
+ setup() {
409
+ const contentContext = injectPopperContentContext();
410
+ const baseSide = vue.computed(() => OPPOSITE_SIDE[contentContext.placedSide.value]);
411
+ return {
412
+ contentContext,
413
+ baseSide
414
+ };
415
+ },
416
+ render() {
417
+ return vue.h("span", {
418
+ ref: (el) => {
419
+ this.contentContext.onArrowChange(el);
420
+ return void 0;
421
+ },
422
+ style: {
423
+ position: "absolute",
424
+ left: this.contentContext.arrowX?.value ? `${this.contentContext.arrowX?.value}px` : void 0,
425
+ top: this.contentContext.arrowY?.value ? `${this.contentContext.arrowY?.value}px` : void 0,
426
+ [this.baseSide]: 0,
427
+ transformOrigin: {
428
+ top: "",
429
+ right: "0 0",
430
+ bottom: "center 0",
431
+ left: "100% 0"
432
+ }[this.contentContext.placedSide.value],
433
+ transform: {
434
+ top: "translateY(100%)",
435
+ right: "translateY(50%) rotate(90deg) translateX(-50%)",
436
+ bottom: `rotate(180deg)`,
437
+ left: "translateY(50%) rotate(-90deg) translateX(50%)"
438
+ }[this.contentContext.placedSide.value],
439
+ visibility: this.contentContext.shouldHideArrow.value ? "hidden" : void 0
440
+ }
441
+ }, () => vue.h(arrow.Arrow, vue.mergeProps(this.$attrs, {
442
+ as: this.$props.as,
443
+ asChild: this.$props.asChild,
444
+ width: this.$props.width,
445
+ height: this.$props.height,
446
+ style: {
447
+ display: "block"
448
+ }
449
+ }), () => this.$slots.default?.()));
450
+ }
451
+ });
452
+
453
+ exports.PopperAnchor = PopperAnchor;
454
+ exports.PopperArrow = PopperArrow;
455
+ exports.PopperContent = PopperContent;
456
+ exports.PopperRoot = PopperRoot;
457
+ exports.popperAnchorProps = popperAnchorProps;
458
+ exports.popperArrowProps = popperArrowProps;
459
+ exports.popperContentEmits = popperContentEmits;
460
+ exports.popperContentProps = popperContentProps;