@txstate-mws/svelte-components 1.1.0 → 1.1.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 (131) hide show
  1. package/actions/buttonify.d.ts +8 -0
  2. package/actions/buttonify.js +26 -0
  3. package/actions/glue.d.ts +16 -0
  4. package/actions/glue.js +82 -0
  5. package/actions/index.d.ts +7 -0
  6. package/actions/index.js +7 -0
  7. package/actions/offset.d.ts +15 -0
  8. package/actions/offset.js +35 -0
  9. package/actions/portal.d.ts +4 -0
  10. package/actions/portal.js +13 -0
  11. package/actions/resize.d.ts +20 -0
  12. package/actions/resize.js +57 -0
  13. package/actions/sticky.d.ts +33 -0
  14. package/actions/sticky.js +85 -0
  15. package/actions/stickyfixed.d.ts +25 -0
  16. package/actions/stickyfixed.js +157 -0
  17. package/components/Card.svelte +23 -0
  18. package/components/Card.svelte.d.ts +18 -0
  19. package/components/CardLayout.svelte +197 -0
  20. package/components/CardLayout.svelte.d.ts +21 -0
  21. package/{src/lib/components → components}/CollapsingTable.svelte +63 -65
  22. package/components/CollapsingTable.svelte.d.ts +59 -0
  23. package/{src/lib/components → components}/ConditionalWrapper.svelte +6 -7
  24. package/components/ConditionalWrapper.svelte.d.ts +23 -0
  25. package/components/FocusLock.svelte +107 -0
  26. package/components/FocusLock.svelte.d.ts +26 -0
  27. package/{src/lib/components → components}/Modal.svelte +22 -24
  28. package/components/Modal.svelte.d.ts +27 -0
  29. package/components/MultiSelect.svelte +179 -0
  30. package/components/MultiSelect.svelte.d.ts +35 -0
  31. package/components/PopupMenu.svelte +287 -0
  32. package/components/PopupMenu.svelte.d.ts +45 -0
  33. package/{src/lib/components → components}/ScreenReaderOnly.svelte +3 -4
  34. package/components/ScreenReaderOnly.svelte.d.ts +20 -0
  35. package/components/index.d.ts +9 -0
  36. package/components/index.js +9 -0
  37. package/index.d.ts +4 -0
  38. package/index.js +4 -0
  39. package/package.json +34 -13
  40. package/types/CardLayout.d.ts +14 -0
  41. package/types/CardLayout.js +1 -0
  42. package/types/CollapsingTable.d.ts +8 -0
  43. package/types/CollapsingTable.js +1 -0
  44. package/types/PopupMenu.d.ts +5 -0
  45. package/types/PopupMenu.js +1 -0
  46. package/types/index.d.ts +3 -0
  47. package/types/index.js +3 -0
  48. package/util/bodyoffset.d.ts +14 -0
  49. package/util/bodyoffset.js +72 -0
  50. package/util/index.d.ts +3 -0
  51. package/util/index.js +3 -0
  52. package/util/repositioned.d.ts +12 -0
  53. package/util/repositioned.js +81 -0
  54. package/util/util.d.ts +5 -0
  55. package/util/util.js +33 -0
  56. package/.eslintrc.cjs +0 -37
  57. package/docs/CollapsingTable.md +0 -79
  58. package/docs/ConditionalWrapper.md +0 -35
  59. package/docs/Modal.md +0 -63
  60. package/docs/PopupMenu.md +0 -31
  61. package/docs/ScreenReaderOnly.md +0 -10
  62. package/docs/actions.md +0 -84
  63. package/docs/stores.md +0 -203
  64. package/playwright.config.js +0 -112
  65. package/src/app.html +0 -14
  66. package/src/global.d.ts +0 -1
  67. package/src/lib/actions/buttonify.ts +0 -27
  68. package/src/lib/actions/glue.ts +0 -96
  69. package/src/lib/actions/index.ts +0 -7
  70. package/src/lib/actions/offset.ts +0 -43
  71. package/src/lib/actions/portal.ts +0 -13
  72. package/src/lib/actions/resize.ts +0 -72
  73. package/src/lib/actions/sticky.ts +0 -105
  74. package/src/lib/actions/stickyfixed.ts +0 -160
  75. package/src/lib/components/Card.svelte +0 -25
  76. package/src/lib/components/CardLayout.svelte +0 -188
  77. package/src/lib/components/FocusLock.svelte +0 -102
  78. package/src/lib/components/MultiSelect.svelte +0 -174
  79. package/src/lib/components/PopupMenu.svelte +0 -285
  80. package/src/lib/components/index.ts +0 -9
  81. package/src/lib/index.ts +0 -4
  82. package/src/lib/types/CardLayout.ts +0 -14
  83. package/src/lib/types/CollapsingTable.ts +0 -8
  84. package/src/lib/types/PopupMenu.ts +0 -5
  85. package/src/lib/types/index.ts +0 -3
  86. package/src/lib/util/bodyoffset.ts +0 -77
  87. package/src/lib/util/index.ts +0 -3
  88. package/src/lib/util/repositioned.ts +0 -91
  89. package/src/lib/util/util.ts +0 -36
  90. package/src/routes/__layout.svelte +0 -125
  91. package/src/routes/actions/index.svelte +0 -1
  92. package/src/routes/actions/offset.svelte +0 -4
  93. package/src/routes/actions/offsetdebounce.svelte +0 -4
  94. package/src/routes/actions/resize.svelte +0 -4
  95. package/src/routes/actions/resizedebounce.svelte +0 -4
  96. package/src/routes/actions/sticky.svelte +0 -4
  97. package/src/routes/cards/index.svelte +0 -4
  98. package/src/routes/cards/preserve.svelte +0 -4
  99. package/src/routes/collapsingtable/index.svelte +0 -4
  100. package/src/routes/collapsingtable/stickyheader.svelte +0 -4
  101. package/src/routes/index.svelte +0 -2
  102. package/src/routes/modal.svelte +0 -4
  103. package/src/routes/multiselect.svelte +0 -4
  104. package/src/routes/popupmenu/body.svelte +0 -4
  105. package/src/routes/popupmenu/complex.svelte +0 -4
  106. package/src/routes/popupmenu/empty.svelte +0 -5
  107. package/src/routes/popupmenu/index.svelte +0 -4
  108. package/src/routes/popupmenu/parent.svelte +0 -4
  109. package/src/routes/popupmenu/scrollable.svelte +0 -27
  110. package/src/routes/popupmenu/showselected.svelte +0 -4
  111. package/src/test/Cards.svelte +0 -64
  112. package/src/test/CollapsingTableTest.svelte +0 -36
  113. package/src/test/ModalTest.svelte +0 -58
  114. package/src/test/MultiSelectTest.svelte +0 -26
  115. package/src/test/Offset.svelte +0 -32
  116. package/src/test/PopupMenuBody.svelte +0 -34
  117. package/src/test/PopupMenuButtonIsParent.svelte +0 -36
  118. package/src/test/PopupMenuComplex.svelte +0 -34
  119. package/src/test/PopupMenuSimple.svelte +0 -36
  120. package/src/test/Resize.svelte +0 -29
  121. package/src/test/Sticky.svelte +0 -65
  122. package/static/layout.css +0 -263
  123. package/svelte.config.js +0 -15
  124. package/tests/.eslintrc.cjs +0 -19
  125. package/tests/CardLayout.test.js +0 -41
  126. package/tests/CollapsingTable.test.js +0 -37
  127. package/tests/Modal.test.js +0 -18
  128. package/tests/MultiSelect.test.js +0 -58
  129. package/tests/PopupMenu.test.js +0 -55
  130. package/tests/common.js +0 -4
  131. package/tsconfig.json +0 -31
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Turns any HTML element into an accessible button that responds to keyboard events and has
3
+ * role="button" and the tabindex attribute set. You are still responsible for making sure it has
4
+ * readable content or a good label.
5
+ */
6
+ export declare function buttonify(node: HTMLElement): {
7
+ destroy(): void;
8
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Turns any HTML element into an accessible button that responds to keyboard events and has
3
+ * role="button" and the tabindex attribute set. You are still responsible for making sure it has
4
+ * readable content or a good label.
5
+ */
6
+ export function buttonify(node) {
7
+ function keydown(e) {
8
+ if (e.key === 'Enter') {
9
+ if (!e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey) {
10
+ e.preventDefault();
11
+ e.stopPropagation();
12
+ node.dispatchEvent(new CustomEvent('click'));
13
+ }
14
+ }
15
+ }
16
+ node.addEventListener('keydown', keydown);
17
+ if (node.tabIndex < 0)
18
+ node.tabIndex = 0;
19
+ node.setAttribute('role', 'button');
20
+ node.style.cursor = 'pointer';
21
+ return {
22
+ destroy() {
23
+ node.removeEventListener('keydown', keydown);
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,16 @@
1
+ import type { SettableSubject } from '@txstate-mws/svelte-store';
2
+ export declare type GlueAlignOpts = 'auto' | 'bottomleft' | 'bottomright' | 'topleft' | 'topright';
3
+ export interface GlueArgs<T extends GlueAlignStore = GlueAlignStore> {
4
+ target: HTMLElement;
5
+ align?: GlueAlignOpts;
6
+ cover?: boolean;
7
+ store?: SettableSubject<T>;
8
+ }
9
+ export interface GlueAlignStore {
10
+ valign: 'bottom' | 'top';
11
+ halign: 'left' | 'right';
12
+ }
13
+ export declare function glue(el: HTMLElement, { target, align, cover, store }: GlueArgs): {
14
+ update({ target: utarget, align: ualign, cover: ucover, store: ustore }: GlueArgs): void;
15
+ destroy: () => void;
16
+ };
@@ -0,0 +1,82 @@
1
+ import { sharedOffsetParent, targetOffset, watchForPositionChangeInContainer } from '../util/index.js';
2
+ let setBodyRelative = false;
3
+ export function glue(el, { target, align = 'auto', cover = false, store }) {
4
+ if (!setBodyRelative) {
5
+ setBodyRelative = true;
6
+ document.body.style.position = 'relative';
7
+ }
8
+ let halign;
9
+ let valign;
10
+ let sharedOffset = sharedOffsetParent(el, target);
11
+ function reposition(offset) {
12
+ if (!target || !sharedOffset || !(el.offsetParent instanceof HTMLElement))
13
+ return;
14
+ let autoalign = align;
15
+ const parentoffset = el.offsetParent === sharedOffset && target !== sharedOffset ? { top: 0, left: 0, bottom: 0, right: 0 } : targetOffset(el.offsetParent, sharedOffset);
16
+ if (align === 'auto') {
17
+ const rect = target.getBoundingClientRect();
18
+ const leftright = window.innerWidth - rect.right > rect.left ? 'left' : 'right';
19
+ const topbottom = window.innerHeight - rect.bottom > rect.top ? 'bottom' : 'top';
20
+ autoalign = topbottom + leftright;
21
+ }
22
+ const targetHeight = target.offsetHeight;
23
+ if (autoalign === 'bottomleft') {
24
+ requestAnimationFrame(() => {
25
+ el.style.top = `${offset.top + (cover ? 0 : targetHeight) - parentoffset.top}px`;
26
+ el.style.left = `${offset.left - parentoffset.left}px`;
27
+ el.style.bottom = '';
28
+ el.style.right = '';
29
+ });
30
+ valign = 'bottom';
31
+ halign = 'left';
32
+ }
33
+ else if (autoalign === 'bottomright') {
34
+ requestAnimationFrame(() => {
35
+ el.style.top = `${offset.top + (cover ? 0 : targetHeight) - parentoffset.top}px`;
36
+ el.style.left = '';
37
+ el.style.bottom = '';
38
+ el.style.right = `${offset.right - parentoffset.right}px`;
39
+ });
40
+ valign = 'bottom';
41
+ halign = 'right';
42
+ }
43
+ else if (autoalign === 'topleft') {
44
+ requestAnimationFrame(() => {
45
+ el.style.top = '';
46
+ el.style.left = `${offset.left - parentoffset.left}px`;
47
+ el.style.bottom = `${offset.bottom + (cover ? 0 : targetHeight) - parentoffset.bottom}px`;
48
+ el.style.right = '';
49
+ });
50
+ valign = 'top';
51
+ halign = 'left';
52
+ }
53
+ else if (autoalign === 'topright') {
54
+ requestAnimationFrame(() => {
55
+ el.style.top = '';
56
+ el.style.left = '';
57
+ el.style.bottom = `${offset.bottom + (cover ? 0 : targetHeight) - parentoffset.bottom}px`;
58
+ el.style.right = `${offset.right - parentoffset.right}px`;
59
+ });
60
+ valign = 'top';
61
+ halign = 'right';
62
+ }
63
+ store?.update(v => ({ ...v, valign, halign }));
64
+ }
65
+ const { destroy, update } = watchForPositionChangeInContainer(target, sharedOffset, reposition);
66
+ return {
67
+ update({ target: utarget, align: ualign = 'auto', cover: ucover = false, store: ustore }) {
68
+ align = ualign;
69
+ cover = ucover;
70
+ if (ustore && ustore !== store) {
71
+ ustore.update(v => ({ ...v, valign, halign }));
72
+ store = ustore;
73
+ }
74
+ if (target !== utarget) {
75
+ sharedOffset = sharedOffsetParent(el, utarget);
76
+ target = utarget;
77
+ update(target, sharedOffset, reposition);
78
+ }
79
+ },
80
+ destroy
81
+ };
82
+ }
@@ -0,0 +1,7 @@
1
+ export * from './buttonify.js';
2
+ export * from './glue.js';
3
+ export * from './offset.js';
4
+ export * from './portal.js';
5
+ export * from './resize.js';
6
+ export * from './sticky.js';
7
+ export * from './stickyfixed.js';
@@ -0,0 +1,7 @@
1
+ export * from './buttonify.js';
2
+ export * from './glue.js';
3
+ export * from './offset.js';
4
+ export * from './portal.js';
5
+ export * from './resize.js';
6
+ export * from './sticky.js';
7
+ export * from './stickyfixed.js';
@@ -0,0 +1,15 @@
1
+ import { Store } from '@txstate-mws/svelte-store';
2
+ import type { WritableSubject } from '@txstate-mws/svelte-store';
3
+ import type { ElementOffsets } from '../util/index.js';
4
+ interface OffsetConfig<T extends ElementOffsets = ElementOffsets> {
5
+ debounce?: boolean | number;
6
+ store?: WritableSubject<T>;
7
+ }
8
+ export declare class OffsetStore extends Store<ElementOffsets> {
9
+ constructor(initialState?: ElementOffsets);
10
+ }
11
+ export declare function offset(el: HTMLElement, config?: OffsetConfig): {
12
+ update(newConfig?: OffsetConfig): void;
13
+ destroy: () => void;
14
+ };
15
+ export {};
@@ -0,0 +1,35 @@
1
+ import { Store } from '@txstate-mws/svelte-store';
2
+ import { debounced, watchForPositionChange } from '../util/index.js';
3
+ export class OffsetStore extends Store {
4
+ constructor(initialState) {
5
+ super(initialState ?? { top: 0, right: 0, bottom: 0, left: 0 });
6
+ }
7
+ }
8
+ export function offset(el, config) {
9
+ if (config?.debounce === true)
10
+ config.debounce = 100;
11
+ let lastOffset = {};
12
+ function lookforoffsetchange(current) {
13
+ lastOffset = current;
14
+ if (config?.store)
15
+ config.store.update(v => ({ ...v, ...current }));
16
+ el.dispatchEvent(new CustomEvent('offset', { detail: current }));
17
+ }
18
+ let resolvedchange = config?.debounce ? debounced(lookforoffsetchange, config.debounce) : lookforoffsetchange;
19
+ const { destroy, update } = watchForPositionChange(el, resolvedchange);
20
+ return {
21
+ update(newConfig) {
22
+ if (newConfig?.debounce === true)
23
+ newConfig.debounce = 100;
24
+ if (newConfig?.store !== config?.store) {
25
+ newConfig?.store?.update(v => ({ ...v, ...lastOffset }));
26
+ }
27
+ if (newConfig?.debounce !== config?.debounce) {
28
+ resolvedchange = newConfig?.debounce ? debounced(lookforoffsetchange, newConfig.debounce) : lookforoffsetchange;
29
+ update(el, resolvedchange);
30
+ }
31
+ config = newConfig;
32
+ },
33
+ destroy
34
+ };
35
+ }
@@ -0,0 +1,4 @@
1
+ export declare function portal(el: HTMLElement, target?: HTMLElement | null): {
2
+ update: (target?: HTMLElement | null) => void;
3
+ destroy: () => void;
4
+ };
@@ -0,0 +1,13 @@
1
+ export function portal(el, target = document.body) {
2
+ function update(target = document.body) {
3
+ target?.appendChild(el);
4
+ }
5
+ function destroy() {
6
+ el.parentElement?.removeChild(el);
7
+ }
8
+ update(target);
9
+ return {
10
+ update,
11
+ destroy
12
+ };
13
+ }
@@ -0,0 +1,20 @@
1
+ import { Store } from '@txstate-mws/svelte-store';
2
+ import type { SettableSubject } from '@txstate-mws/svelte-store';
3
+ export interface ElementSize {
4
+ clientWidth?: number;
5
+ clientHeight?: number;
6
+ offsetWidth?: number;
7
+ offsetHeight?: number;
8
+ }
9
+ interface ResizeConfig {
10
+ debounce?: boolean | number;
11
+ store?: SettableSubject<ElementSize>;
12
+ }
13
+ export declare class ResizeStore extends Store<ElementSize> {
14
+ constructor(initialState?: ElementSize);
15
+ }
16
+ export declare function resize(el: HTMLElement, config?: ResizeConfig): void | {
17
+ update(newConfig?: ResizeConfig): void;
18
+ destroy(): void;
19
+ };
20
+ export {};
@@ -0,0 +1,57 @@
1
+ import { equal } from 'txstate-utils';
2
+ import { Store } from '@txstate-mws/svelte-store';
3
+ import { debounced } from '../util/index.js';
4
+ export class ResizeStore extends Store {
5
+ constructor(initialState) {
6
+ super(initialState ?? typeof window === 'undefined'
7
+ ? {
8
+ clientWidth: 1024,
9
+ clientHeight: 1024,
10
+ offsetWidth: 1024,
11
+ offsetHeight: 1024
12
+ }
13
+ : {
14
+ clientWidth: window.innerWidth,
15
+ clientHeight: window.innerHeight,
16
+ offsetWidth: window.innerWidth,
17
+ offsetHeight: window.innerHeight
18
+ });
19
+ }
20
+ }
21
+ export function resize(el, config) {
22
+ if (config?.debounce === true)
23
+ config.debounce = 100;
24
+ let lastSize = {};
25
+ function watch() {
26
+ const current = { clientWidth: el.clientWidth, clientHeight: el.clientHeight, offsetWidth: el.offsetWidth, offsetHeight: el.offsetHeight };
27
+ if (!equal(lastSize, current)) {
28
+ lastSize = current;
29
+ if (config?.store)
30
+ config.store.set(current);
31
+ el.dispatchEvent(new CustomEvent('resize', { detail: current }));
32
+ }
33
+ }
34
+ if (typeof ResizeObserver === 'undefined')
35
+ return watch(); // support SSR
36
+ let observer = new ResizeObserver(config?.debounce ? debounced(watch, config.debounce) : watch);
37
+ observer.observe(el);
38
+ watch();
39
+ return {
40
+ update(newConfig) {
41
+ if (newConfig?.debounce === true)
42
+ newConfig.debounce = 100;
43
+ if (newConfig?.store !== config?.store) {
44
+ newConfig?.store?.set(lastSize);
45
+ }
46
+ if (!!newConfig?.debounce !== !!config?.debounce) {
47
+ observer.disconnect();
48
+ observer = new ResizeObserver(newConfig?.debounce ? debounced(watch, newConfig.debounce) : watch);
49
+ observer.observe(el);
50
+ }
51
+ config = newConfig;
52
+ },
53
+ destroy() {
54
+ observer.disconnect();
55
+ }
56
+ };
57
+ }
@@ -0,0 +1,33 @@
1
+ import type { SettableSubject } from '@txstate-mws/svelte-store';
2
+ export interface StickyArgs<T extends StickyStore = StickyStore> {
3
+ target?: HTMLElement;
4
+ store?: SettableSubject<T>;
5
+ }
6
+ export interface StickyStore {
7
+ floating?: boolean;
8
+ translateY?: number;
9
+ }
10
+ /**
11
+ * A robust implementation of stickyness based on `transform: translateY()`. Only use when
12
+ * `position: sticky` won't work for you. If `position: sticky` does work for you, use it,
13
+ * it performs better.
14
+ *
15
+ * Reasons `position: sticky` might not work:
16
+ * 1) it cannot work on a thead or tr, only td and th (and non-table elements)
17
+ * 2) it gets confused if any parent elements have an `overflow` set, even `overflow: hidden`
18
+ * 3) it always stays within the boundaries of the closest parent element that has a `position`
19
+ * * use:sticky accepts a `target` element as a parameter, defining the bounding container
20
+ *
21
+ * Note that if the element you place this on has other transforms, this is going to overwrite them.
22
+ *
23
+ * The main drawback to this approach is that it cannot do its work before scroll on mobile, only after,
24
+ * so on mobile you are going to see it visually lag a little during scrolling.
25
+ *
26
+ * You may also want to try use:stickyfixed. It is based on setting `position: fixed` when appropriate,
27
+ * and therefore animates better than this. However, it has a lot more work to do and is more
28
+ * fragile if your markup/CSS situation is very complex.
29
+ */
30
+ export declare function sticky(el: HTMLElement, config?: StickyArgs): {
31
+ update: (newConfig?: StickyArgs) => void;
32
+ destroy: () => void;
33
+ };
@@ -0,0 +1,85 @@
1
+ import { bodyOffset, watchForPositionChange } from '../util/index.js';
2
+ let scrolled = false;
3
+ /**
4
+ * A robust implementation of stickyness based on `transform: translateY()`. Only use when
5
+ * `position: sticky` won't work for you. If `position: sticky` does work for you, use it,
6
+ * it performs better.
7
+ *
8
+ * Reasons `position: sticky` might not work:
9
+ * 1) it cannot work on a thead or tr, only td and th (and non-table elements)
10
+ * 2) it gets confused if any parent elements have an `overflow` set, even `overflow: hidden`
11
+ * 3) it always stays within the boundaries of the closest parent element that has a `position`
12
+ * * use:sticky accepts a `target` element as a parameter, defining the bounding container
13
+ *
14
+ * Note that if the element you place this on has other transforms, this is going to overwrite them.
15
+ *
16
+ * The main drawback to this approach is that it cannot do its work before scroll on mobile, only after,
17
+ * so on mobile you are going to see it visually lag a little during scrolling.
18
+ *
19
+ * You may also want to try use:stickyfixed. It is based on setting `position: fixed` when appropriate,
20
+ * and therefore animates better than this. However, it has a lot more work to do and is more
21
+ * fragile if your markup/CSS situation is very complex.
22
+ */
23
+ export function sticky(el, config) {
24
+ let floating;
25
+ let translateY;
26
+ let scrollstart = Number.MAX_SAFE_INTEGER;
27
+ let scrollend = Number.MAX_SAFE_INTEGER;
28
+ function recalc(scroll) {
29
+ const newTranslateY = Math.max(Math.min(scroll, scrollend) - scrollstart, 0);
30
+ if (newTranslateY !== translateY) {
31
+ el.style.transform = `translateY(${newTranslateY}px)`;
32
+ translateY = newTranslateY;
33
+ floating = scroll >= scrollstart;
34
+ config?.store?.update(v => ({ ...v, translateY, floating }));
35
+ }
36
+ }
37
+ function onscroll(_) {
38
+ recalc(window.scrollY);
39
+ }
40
+ function onwheel(e) {
41
+ e.preventDefault();
42
+ const scroll = window.scrollY + e.deltaY;
43
+ recalc(scroll);
44
+ if (!scrolled) {
45
+ window.scrollTo({ top: scroll });
46
+ scrolled = true;
47
+ setTimeout(() => {
48
+ scrolled = false;
49
+ }, 0);
50
+ }
51
+ }
52
+ let lastOffsetParent = el.offsetParent;
53
+ function onpositionchange(offset) {
54
+ const offsetParent = el.offsetParent;
55
+ if (!(offsetParent instanceof HTMLElement))
56
+ return;
57
+ if (lastOffsetParent !== offsetParent) {
58
+ offset[0] = bodyOffset(offsetParent);
59
+ update(offsetParent, onpositionchange);
60
+ lastOffsetParent = offsetParent;
61
+ }
62
+ scrollstart = offset[0].top + el.offsetTop;
63
+ const containeroffset = config?.target ? offset[1] : offset[0];
64
+ scrollend = Math.max(scrollstart, containeroffset.top + (config?.target ?? offsetParent).clientHeight - el.offsetHeight);
65
+ recalc(window.scrollY);
66
+ }
67
+ const { destroy, update } = watchForPositionChange([el.offsetParent, config?.target], onpositionchange);
68
+ window.addEventListener('scroll', onscroll, { passive: true });
69
+ window.addEventListener('wheel', onwheel, { passive: false });
70
+ function stickyUpdate(newConfig) {
71
+ if (newConfig?.store !== config?.store)
72
+ newConfig?.store?.update(v => ({ ...v, translateY, floating }));
73
+ config = newConfig;
74
+ update([el.offsetParent, config?.target], onpositionchange);
75
+ }
76
+ function stickyDestroy() {
77
+ window.removeEventListener('scroll', onscroll);
78
+ window.removeEventListener('wheel', onwheel);
79
+ destroy();
80
+ }
81
+ return {
82
+ update: stickyUpdate,
83
+ destroy: stickyDestroy
84
+ };
85
+ }
@@ -0,0 +1,25 @@
1
+ import type { StickyArgs } from './sticky.js';
2
+ /**
3
+ * An implementation of stickyness based on `positon: fixed`. Only use when
4
+ * `position: sticky` won't work for you. If `position: sticky` does work for you, use it,
5
+ * it performs better.
6
+ *
7
+ * Reasons `position: sticky` might not work:
8
+ * 1) it cannot work on a thead or tr, only td and th (and non-table elements)
9
+ * 2) it gets confused if any parent elements have an `overflow` set, even `overflow: hidden`
10
+ * 3) it always stays within the boundaries of the closest parent element that has a `position`
11
+ * * use:sticky accepts a `target` element as a parameter, defining the bounding container
12
+ *
13
+ * Note that this implementation gets great animation performance at the cost of extra complexity.
14
+ * It does a lot of work like cloning the element you place it on, placing the cloned element next
15
+ * to yours in the DOM, setting widths to match between them, watching for mutations to keep them
16
+ * in sync, etc. Some of this work may get foiled if you have a complex html/css/js situation.
17
+ *
18
+ * You may also want to try use:sticky. It is based on setting `transform: translateY()` as appropriate,
19
+ * and is less fragile than this. However, it cannot do its work before the scroll on mobile, only after,
20
+ * so it will visually lag a bit on mobile.
21
+ */
22
+ export declare function stickyfixed(el: HTMLElement, config?: StickyArgs): {
23
+ update: (newConfig?: StickyArgs) => void;
24
+ destroy: () => void;
25
+ };
@@ -0,0 +1,157 @@
1
+ import { bodyOffset, watchForPositionChange } from '../util/index.js';
2
+ /**
3
+ * An implementation of stickyness based on `positon: fixed`. Only use when
4
+ * `position: sticky` won't work for you. If `position: sticky` does work for you, use it,
5
+ * it performs better.
6
+ *
7
+ * Reasons `position: sticky` might not work:
8
+ * 1) it cannot work on a thead or tr, only td and th (and non-table elements)
9
+ * 2) it gets confused if any parent elements have an `overflow` set, even `overflow: hidden`
10
+ * 3) it always stays within the boundaries of the closest parent element that has a `position`
11
+ * * use:sticky accepts a `target` element as a parameter, defining the bounding container
12
+ *
13
+ * Note that this implementation gets great animation performance at the cost of extra complexity.
14
+ * It does a lot of work like cloning the element you place it on, placing the cloned element next
15
+ * to yours in the DOM, setting widths to match between them, watching for mutations to keep them
16
+ * in sync, etc. Some of this work may get foiled if you have a complex html/css/js situation.
17
+ *
18
+ * You may also want to try use:sticky. It is based on setting `transform: translateY()` as appropriate,
19
+ * and is less fragile than this. However, it cannot do its work before the scroll on mobile, only after,
20
+ * so it will visually lag a bit on mobile.
21
+ */
22
+ export function stickyfixed(el, config) {
23
+ let floating;
24
+ let translateY;
25
+ let scrollstart = Number.MAX_SAFE_INTEGER;
26
+ let scrollend = Number.MAX_SAFE_INTEGER;
27
+ let spacer;
28
+ let elheight;
29
+ let left;
30
+ let top = 0;
31
+ function generatespacer(mutations) {
32
+ if (mutations?.every(m => m.attributeName === 'style'))
33
+ return;
34
+ spacer?.remove();
35
+ spacer = el.cloneNode(true);
36
+ spacer.setAttribute('aria-hidden', 'true');
37
+ spacer.style.position = '';
38
+ spacer.style.top = '';
39
+ spacer.style.left = '';
40
+ spacer.style.width = '';
41
+ const tds = Array.from(spacer.querySelectorAll('td,th'));
42
+ for (const td of tds)
43
+ td.style.width = '';
44
+ if (floating)
45
+ el.parentElement?.insertBefore(spacer, el);
46
+ }
47
+ const mutationobserver = new MutationObserver(generatespacer);
48
+ mutationobserver.observe(el, {
49
+ subtree: true,
50
+ childList: true,
51
+ attributes: true,
52
+ characterData: true
53
+ });
54
+ generatespacer();
55
+ function adjustwidths() {
56
+ const tds = Array.from(el.querySelectorAll('td,th'));
57
+ if (floating) {
58
+ const spacertds = Array.from(spacer.querySelectorAll('td,th'));
59
+ const widths = spacertds.map(td => window.getComputedStyle(td).width);
60
+ const table = spacer.closest('table');
61
+ let extrawidth = 0;
62
+ if (table) {
63
+ const cell = el.querySelector('th:last-child,td:last-child');
64
+ if (cell) {
65
+ const tstyle = window.getComputedStyle(table);
66
+ extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth) : 0;
67
+ }
68
+ }
69
+ const width = `${parseInt(window.getComputedStyle(spacer).width) + extrawidth}px`;
70
+ elheight = spacer.offsetHeight;
71
+ for (let i = 0; i < tds.length; i++)
72
+ tds[i].style.width = widths[i];
73
+ el.style.width = width;
74
+ }
75
+ else {
76
+ for (const td of tds)
77
+ td.style.width = '';
78
+ el.style.width = '';
79
+ elheight = el.offsetHeight;
80
+ }
81
+ }
82
+ const resizeobserver = new ResizeObserver(adjustwidths);
83
+ function recalc(scroll) {
84
+ const newTranslateY = Math.max(Math.min(scroll - 1, scrollend) - scrollstart, 0);
85
+ const newFloating = newTranslateY > 0;
86
+ const newTop = Math.max(Math.min(scrollend - scroll, 0), -2 * elheight);
87
+ if (newFloating !== floating) {
88
+ floating = newFloating;
89
+ if (floating) {
90
+ el.parentElement?.insertBefore(spacer, el);
91
+ el.style.position = 'fixed';
92
+ el.style.top = `${Math.min(scrollend - scroll, 0)}px`;
93
+ el.style.left = `${left}px`;
94
+ resizeobserver.observe(spacer);
95
+ }
96
+ else {
97
+ el.style.position = '';
98
+ el.style.top = '';
99
+ el.style.left = '';
100
+ resizeobserver.disconnect();
101
+ spacer.remove();
102
+ }
103
+ adjustwidths();
104
+ }
105
+ if (newTranslateY !== translateY) {
106
+ translateY = newTranslateY;
107
+ config?.store?.update(v => ({ ...v, translateY, floating }));
108
+ }
109
+ if (top !== newTop) {
110
+ top = newTop;
111
+ if (floating)
112
+ el.style.top = `${top}px`;
113
+ }
114
+ }
115
+ function onscroll(_) {
116
+ recalc(window.scrollY);
117
+ }
118
+ let lastOffsetParent = el.offsetParent;
119
+ function onpositionchange(offset) {
120
+ const offsetParent = el.offsetParent;
121
+ if (!(offsetParent instanceof HTMLElement))
122
+ return;
123
+ if (lastOffsetParent !== offsetParent) {
124
+ offset[0] = bodyOffset(offsetParent);
125
+ update(offsetParent, onpositionchange);
126
+ lastOffsetParent = offsetParent;
127
+ }
128
+ const table = el.closest('table');
129
+ const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing) : 0);
130
+ scrollstart = offset[0].top + el.offsetTop;
131
+ left = offset[0].left + (spacer.parentElement ? spacer.offsetLeft : el.offsetLeft);
132
+ if (el.tagName === 'TR') {
133
+ scrollstart -= borderspacing;
134
+ }
135
+ const containeroffset = config?.target ? offset[1] ?? offset[0] : offset[0];
136
+ scrollend = Math.max(scrollstart, containeroffset.top + (config?.target ?? offsetParent).clientHeight - el.offsetHeight);
137
+ recalc(window.scrollY);
138
+ }
139
+ const { destroy, update } = watchForPositionChange([el.offsetParent, config?.target], onpositionchange);
140
+ window.addEventListener('scroll', onscroll, { passive: true });
141
+ function stickyUpdate(newConfig) {
142
+ if (newConfig?.store !== config?.store)
143
+ newConfig?.store?.update(v => ({ ...v, translateY, floating }));
144
+ config = newConfig;
145
+ update([el.offsetParent, config?.target], onpositionchange);
146
+ }
147
+ function stickyDestroy() {
148
+ resizeobserver.disconnect();
149
+ mutationobserver.disconnect();
150
+ window.removeEventListener('scroll', onscroll);
151
+ destroy();
152
+ }
153
+ return {
154
+ update: stickyUpdate,
155
+ destroy: stickyDestroy
156
+ };
157
+ }
@@ -0,0 +1,23 @@
1
+ <script >export let className = '';
2
+ import { getContext } from 'svelte';
3
+ import { resize } from '../actions';
4
+ import { CARDLAYOUT } from '../types';
5
+ const block = { element: undefined };
6
+ const { registerBlock, gutter, recalculate } = getContext(CARDLAYOUT);
7
+ const { width, order, linebreak } = registerBlock(block);
8
+ </script>
9
+
10
+ <li class="cardlayout-card {className}" bind:this={block.element} use:resize on:resize={recalculate} style="margin-bottom: {$gutter}px; width: {$width}; order: {$order};">
11
+ <slot></slot>
12
+ </li>
13
+ {#if $linebreak}
14
+ <li aria-hidden="true" class="cardlayout-columnbreak" style:order={$order}></li>
15
+ {/if}
16
+
17
+ <style>
18
+ .cardlayout-columnbreak {
19
+ width: 0;
20
+ height: 100%;
21
+ visibility: hidden;
22
+ }
23
+ </style>
@@ -0,0 +1,18 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ className?: string;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {
10
+ default: {};
11
+ };
12
+ };
13
+ export declare type CardProps = typeof __propDef.props;
14
+ export declare type CardEvents = typeof __propDef.events;
15
+ export declare type CardSlots = typeof __propDef.slots;
16
+ export default class Card extends SvelteComponentTyped<CardProps, CardEvents, CardSlots> {
17
+ }
18
+ export {};