@txstate-mws/svelte-components 1.6.11 → 1.6.12

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.
@@ -7,15 +7,15 @@ class ElementQueries {
7
7
  #subscribers = 0;
8
8
  #stores = new Map();
9
9
  #processwidths() {
10
- for (let i = 0; i < this.#watchlist.length; i++) {
10
+ for (const layer of this.#watchlist) {
11
11
  const widths = [];
12
12
  const attrs = [];
13
- for (let j = 0; j < this.#watchlist[i].length; j++) {
14
- widths.push(this.#watchlist[i][j].offsetWidth);
15
- attrs.push(this.#watchlist[i][j].getAttribute('data-eq'));
13
+ for (const el of layer) {
14
+ widths.push(el.offsetWidth);
15
+ attrs.push(el.getAttribute('data-eq'));
16
16
  }
17
- for (let j = 0; j < this.#watchlist[i].length; j++) {
18
- const el = this.#watchlist[i][j];
17
+ for (let j = 0; j < layer.length; j++) {
18
+ const el = layer[j];
19
19
  const w = widths[j];
20
20
  let attrstr = '';
21
21
  let finalw = 1600;
@@ -62,10 +62,10 @@ class ElementQueries {
62
62
  #gotoanchor() {
63
63
  // since we are dramatically resizing, we need to scroll to the proper place for an
64
64
  // anchor tag
65
- const m = document.location.hash.match(/^#([a-z][\w:.-]*)$/i);
65
+ const m = document.location.hash.match(/^#([a-z][a-zA-Z0-9_:.\-]*)$/iv);
66
66
  if (m) {
67
67
  const id = m[1];
68
- requestAnimationFrame(() => { (document.getElementById(id) || document.querySelector(`[name="${id}"]`))?.scrollIntoView(true); });
68
+ requestAnimationFrame(() => { (document.getElementById(id) ?? document.querySelector(`[name="${id}"]`))?.scrollIntoView(true); });
69
69
  }
70
70
  }
71
71
  fullresync() {
@@ -100,7 +100,7 @@ class ElementQueries {
100
100
  // instead of faithfully subscribing and unsubscribing, so we'll add a subscriber that can't be
101
101
  // decremented and it will stay activated forever
102
102
  activate() {
103
- this.#subscribers++;
103
+ this.#subscribers += 1;
104
104
  this.#activateRaw();
105
105
  }
106
106
  #deactivate() {
@@ -111,14 +111,15 @@ class ElementQueries {
111
111
  this.#hasInit = false;
112
112
  }
113
113
  subscribe(el, store) {
114
- this.#subscribers++;
114
+ this.#subscribers += 1;
115
115
  if (store)
116
116
  this.#stores.set(el, store);
117
117
  else
118
118
  this.#stores.delete(el);
119
119
  this.#activateRaw();
120
120
  return () => {
121
- if (--this.#subscribers === 0)
121
+ this.#subscribers -= 1;
122
+ if (this.#subscribers === 0)
122
123
  this.#deactivate();
123
124
  };
124
125
  }
@@ -1,17 +1,74 @@
1
1
  import type { SettableSubject } from '@txstate-mws/svelte-store';
2
- export type GlueAlignOpts = 'auto' | 'bottomleft' | 'bottomright' | 'topleft' | 'topright';
2
+ export type GlueAlignOpts = 'auto' | 'bottomleft' | 'bottomright' | 'topleft' | 'topright' | 'top' | 'bottom' | 'left' | 'right' | 'autoleft' | 'autoright' | 'topauto' | 'bottomauto' | 'automiddle' | 'middleauto' | 'middle';
3
3
  export interface GlueArgs<T extends GlueAlignStore = GlueAlignStore> {
4
- target: HTMLElement;
4
+ /**
5
+ * The HTML element to which the element should be glued. The target remains where
6
+ * it is; the element you placed the glue action on will become position: fixed and
7
+ * move to align with the target.
8
+ */
9
+ target?: HTMLElement;
10
+ /**
11
+ * How to align the element relative to the target.
12
+ *
13
+ * Corner alignments:
14
+ * - `topleft`: element's bottom-left corner meets target's top-left corner
15
+ * - `topright`: element's bottom-right corner meets target's top-right corner
16
+ * - `bottomleft`: element's top-left corner meets target's bottom-left corner
17
+ * - `bottomright`: element's top-right corner meets target's bottom-right corner
18
+ *
19
+ * Side alignments (centered on the midpoint of the side):
20
+ * - `top`: element centered above target
21
+ * - `bottom`: element centered below target
22
+ * - `left`: element centered to the left of target
23
+ * - `right`: element centered to the right of target
24
+ *
25
+ * Auto alignments (choose based on available viewport space):
26
+ * - `auto`: choose between all four corners
27
+ * - `autoleft`: choose between `topleft` and `bottomleft`
28
+ * - `autoright`: choose between `topright` and `bottomright`
29
+ * - `topauto`: choose between `topleft` and `topright`
30
+ * - `bottomauto`: choose between `bottomleft` and `bottomright`
31
+ * - `automiddle`: choose between `top` and `bottom`
32
+ * - `middleauto`: choose between `left` and `right`
33
+ * - `middle`: choose between `top`, `bottom`, `left`, and `right`, or with `cover` set to true, simply center over the target
34
+ *
35
+ * When `cover` is false (default), the element is placed adjacent to the target.
36
+ * When `cover` is true, the element overlaps the target from the aligned side.
37
+ *
38
+ * @default 'auto'
39
+ */
5
40
  align?: GlueAlignOpts;
41
+ /**
42
+ * Adjusts the alignment setting behavior such that now the element will cover the target.
43
+ */
6
44
  cover?: boolean;
45
+ /**
46
+ * A pixel margin between the element and the target. When used with `cover`, will reveal
47
+ * a gap's worth of pixels of the target.
48
+ */
7
49
  gap?: number;
50
+ /**
51
+ * When true, increases the parent element's `min-height` to account for
52
+ * the positioned element's size, preventing the parent from collapsing
53
+ * when its child becomes `position: fixed`. Ignored if the parent has
54
+ * `overflow-y: auto`. The original `min-height` is restored on destroy.
55
+ *
56
+ * @default false
57
+ */
8
58
  adjustparentheight?: boolean;
59
+ /**
60
+ * The store that contains information about the alignment of the element. With align
61
+ * set to `auto`, this will tell you quickly which alignment is currently active.
62
+ */
9
63
  store?: SettableSubject<T>;
10
64
  }
11
65
  export interface GlueAlignStore {
12
- valign: 'bottom' | 'top';
13
- halign: 'left' | 'right';
66
+ valign: 'bottom' | 'top' | 'center';
67
+ halign: 'left' | 'right' | 'center';
14
68
  }
69
+ /**
70
+ * Make this element `position: fixed` and move it around to align with the target element.
71
+ */
15
72
  export declare function glue(el: HTMLElement, { target, align, cover, gap, adjustparentheight, store }: GlueArgs): {
16
73
  update({ target: utarget, align: ualign, cover: ucover, gap: ugap, store: ustore }: GlueArgs): void;
17
74
  destroy(): void;
@@ -1,22 +1,23 @@
1
1
  import { watchForMutations } from '../util/index.js';
2
2
  import { equal } from 'txstate-utils';
3
+ /**
4
+ * Make this element `position: fixed` and move it around to align with the target element.
5
+ */
3
6
  export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjustparentheight = false, store }) {
4
7
  let halign;
5
8
  let valign;
6
- const parent = el.offsetParent;
9
+ const parent = el.offsetParent instanceof HTMLElement ? el.offsetParent : undefined;
7
10
  const formerMinHeight = parent?.style.minHeight;
8
11
  if (target)
9
12
  el.style.position = 'fixed';
10
- function adjustParentHeight() {
13
+ function adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY) {
11
14
  if (!adjustparentheight)
12
15
  return;
13
- if (parent?.style.overflowY === 'auto')
16
+ if (parentOverflowY === 'auto')
14
17
  return;
15
- const minHeight = el.offsetTop + el.offsetHeight;
16
- if (minHeight > (parent?.clientHeight ?? 0)) {
17
- requestAnimationFrame(() => {
18
- parent.style.minHeight = String(minHeight) + 'px';
19
- });
18
+ const minHeight = elOffsetTop + elOffsetHeight;
19
+ if (minHeight > parentClientHeight) {
20
+ parent.style.minHeight = String(minHeight) + 'px';
20
21
  }
21
22
  }
22
23
  let lastrect;
@@ -34,13 +35,55 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
34
35
  const rect = { left: tmpRect.left - fixedRect.left, right: document.documentElement.clientWidth - tmpRect.right - fixedRect.right, top: tmpRect.top - fixedRect.top, bottom: document.documentElement.clientHeight - tmpRect.bottom - fixedRect.bottom, width: tmpRect.width, height: tmpRect.height };
35
36
  if (equal(rect, lastrect))
36
37
  return;
38
+ lastrect = rect;
37
39
  let autoalign = align;
40
+ const leftright = rect.right > rect.left ? 'left' : 'right';
41
+ const topbottom = rect.bottom > rect.top ? 'bottom' : 'top';
38
42
  if (align === 'auto') {
39
- const leftright = rect.right > rect.left ? 'left' : 'right';
40
- const topbottom = rect.bottom > rect.top ? 'bottom' : 'top';
41
43
  autoalign = topbottom + leftright;
42
44
  }
45
+ else if (align === 'autoleft') {
46
+ autoalign = topbottom + 'left';
47
+ }
48
+ else if (align === 'autoright') {
49
+ autoalign = topbottom + 'right';
50
+ }
51
+ else if (align === 'topauto') {
52
+ autoalign = 'top' + leftright;
53
+ }
54
+ else if (align === 'bottomauto') {
55
+ autoalign = 'bottom' + leftright;
56
+ }
57
+ else if (align === 'automiddle') {
58
+ autoalign = topbottom;
59
+ }
60
+ else if (align === 'middleauto') {
61
+ autoalign = leftright;
62
+ }
63
+ else if (align === 'middle') {
64
+ if (cover) {
65
+ autoalign = 'middle';
66
+ }
67
+ else {
68
+ const maxSpace = Math.max(rect.top, rect.bottom, rect.left, rect.right);
69
+ if (maxSpace === rect.bottom)
70
+ autoalign = 'bottom';
71
+ else if (maxSpace === rect.top)
72
+ autoalign = 'top';
73
+ else if (maxSpace === rect.right)
74
+ autoalign = 'right';
75
+ else
76
+ autoalign = 'left';
77
+ }
78
+ }
43
79
  const targetHeight = rect.height;
80
+ const elRect = el.getBoundingClientRect();
81
+ const elWidth = elRect.width;
82
+ const elHeight = elRect.height;
83
+ const elOffsetTop = el.offsetTop;
84
+ const elOffsetHeight = el.offsetHeight;
85
+ const parentClientHeight = parent?.clientHeight ?? 0;
86
+ const parentOverflowY = parent?.style.overflowY;
44
87
  if (autoalign === 'bottomleft') {
45
88
  cancelAnimationFrame(timer);
46
89
  timer = requestAnimationFrame(() => {
@@ -48,7 +91,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
48
91
  el.style.left = `${rect.left}px`;
49
92
  el.style.removeProperty('bottom');
50
93
  el.style.removeProperty('right');
51
- adjustParentHeight();
94
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
52
95
  });
53
96
  valign = 'bottom';
54
97
  halign = 'left';
@@ -60,7 +103,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
60
103
  el.style.removeProperty('left');
61
104
  el.style.removeProperty('bottom');
62
105
  el.style.right = `${rect.right}px`;
63
- adjustParentHeight();
106
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
64
107
  });
65
108
  valign = 'bottom';
66
109
  halign = 'right';
@@ -72,7 +115,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
72
115
  el.style.left = `${rect.left}px`;
73
116
  el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
74
117
  el.style.removeProperty('right');
75
- adjustParentHeight();
118
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
76
119
  });
77
120
  valign = 'top';
78
121
  halign = 'left';
@@ -84,14 +127,85 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
84
127
  el.style.removeProperty('left');
85
128
  el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
86
129
  el.style.right = `${rect.right}px`;
87
- adjustParentHeight();
130
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
88
131
  });
89
132
  valign = 'top';
90
133
  halign = 'right';
91
134
  }
135
+ else if (autoalign === 'top') {
136
+ cancelAnimationFrame(timer);
137
+ timer = requestAnimationFrame(() => {
138
+ el.style.removeProperty('top');
139
+ el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
140
+ el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
141
+ el.style.removeProperty('right');
142
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
143
+ });
144
+ valign = 'top';
145
+ halign = 'center';
146
+ }
147
+ else if (autoalign === 'bottom') {
148
+ cancelAnimationFrame(timer);
149
+ timer = requestAnimationFrame(() => {
150
+ el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
151
+ el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
152
+ el.style.removeProperty('bottom');
153
+ el.style.removeProperty('right');
154
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
155
+ });
156
+ valign = 'bottom';
157
+ halign = 'center';
158
+ }
159
+ else if (autoalign === 'left') {
160
+ cancelAnimationFrame(timer);
161
+ timer = requestAnimationFrame(() => {
162
+ el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
163
+ el.style.removeProperty('bottom');
164
+ if (cover) {
165
+ el.style.left = `${rect.left + gap}px`;
166
+ el.style.removeProperty('right');
167
+ }
168
+ else {
169
+ el.style.removeProperty('left');
170
+ el.style.right = `${rect.right + rect.width + gap}px`;
171
+ }
172
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
173
+ });
174
+ valign = 'center';
175
+ halign = 'left';
176
+ }
177
+ else if (autoalign === 'right') {
178
+ cancelAnimationFrame(timer);
179
+ timer = requestAnimationFrame(() => {
180
+ el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
181
+ el.style.removeProperty('bottom');
182
+ if (cover) {
183
+ el.style.removeProperty('left');
184
+ el.style.right = `${rect.right + gap}px`;
185
+ }
186
+ else {
187
+ el.style.left = `${rect.left + rect.width + gap}px`;
188
+ el.style.removeProperty('right');
189
+ }
190
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
191
+ });
192
+ valign = 'center';
193
+ halign = 'right';
194
+ }
195
+ else if (autoalign === 'middle') {
196
+ cancelAnimationFrame(timer);
197
+ timer = requestAnimationFrame(() => {
198
+ el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
199
+ el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
200
+ el.style.removeProperty('bottom');
201
+ el.style.removeProperty('right');
202
+ adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
203
+ });
204
+ valign = 'center';
205
+ halign = 'center';
206
+ }
92
207
  store?.update(v => ({ ...v, valign, halign }));
93
208
  }
94
- // eslint-disable-next-line @typescript-eslint/unbound-method
95
209
  const { destroy: watchDestroy } = watchForMutations(reposition);
96
210
  document.addEventListener('scroll', reposition, { capture: true });
97
211
  return {
@@ -116,7 +230,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
116
230
  destroy() {
117
231
  watchDestroy();
118
232
  document.removeEventListener('scroll', reposition);
119
- if (adjustparentheight) {
233
+ if (adjustparentheight && parent) {
120
234
  if (formerMinHeight)
121
235
  parent.style.minHeight = formerMinHeight;
122
236
  parent.style.removeProperty('min-height');
@@ -16,11 +16,11 @@ export function offset(el, config) {
16
16
  el.dispatchEvent(new CustomEvent('offset', { detail: current }));
17
17
  }
18
18
  let resolvedchange = config?.debounce ? debounced(lookforoffsetchange, config.debounce) : lookforoffsetchange;
19
- const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent ?? document.body : config.container);
19
+ const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent instanceof HTMLElement ? el.offsetParent : document.body : config.container);
20
20
  const { destroy, update } = watchForPositionChangeInContainer(el, offsetContainer, resolvedchange);
21
21
  return {
22
22
  update(newConfig) {
23
- const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent ?? document.body : config.container);
23
+ const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent instanceof HTMLElement ? el.offsetParent : document.body : config.container);
24
24
  if (newConfig?.debounce === true)
25
25
  newConfig.debounce = 100;
26
26
  if (newConfig?.store !== config?.store) {
@@ -6,5 +6,5 @@ export type SvelteHTMLActionType<P = any> = (node: HTMLElement, params?: P) => S
6
6
  export type HTMLActionEntry<P = any> = SvelteHTMLActionType<P> | [SvelteHTMLActionType<P>, P];
7
7
  export type SvelteSVGActionType<P = any> = (node: SVGElement, params?: P) => SvelteActionReturnType<P> | undefined;
8
8
  export type SVGActionEntry<P = any> = SvelteSVGActionType<P> | [SvelteSVGActionType<P>, P];
9
- export declare function passActions(node: HTMLElement, actions: HTMLActionEntry[]): SvelteActionReturnType<HTMLActionEntry[]>;
10
- export declare function passActions(node: SVGElement, actions: SVGActionEntry[]): SvelteActionReturnType<SVGActionEntry[]>;
9
+ export declare function passActions(node: HTMLElement, actions: HTMLActionEntry[] | undefined): SvelteActionReturnType<HTMLActionEntry[]>;
10
+ export declare function passActions(node: SVGElement, actions: SVGActionEntry[] | undefined): SvelteActionReturnType<SVGActionEntry[]>;
@@ -63,10 +63,10 @@ export function stickyfixed(el, config) {
63
63
  const cell = el.querySelector('th:last-child,td:last-child');
64
64
  if (cell) {
65
65
  const tstyle = window.getComputedStyle(table);
66
- extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth) : 0;
66
+ extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth, 10) : 0;
67
67
  }
68
68
  }
69
- const width = `${parseInt(window.getComputedStyle(spacer).width) + extrawidth}px`;
69
+ const width = `${parseInt(window.getComputedStyle(spacer).width, 10) + extrawidth}px`;
70
70
  elheight = spacer.offsetHeight;
71
71
  for (let i = 0; i < tds.length; i++)
72
72
  tds[i].style.width = widths[i];
@@ -126,7 +126,7 @@ export function stickyfixed(el, config) {
126
126
  lastOffsetParent = offsetParent;
127
127
  }
128
128
  const table = el.closest('table');
129
- const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing) : 0);
129
+ const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing, 10) : 0);
130
130
  scrollstart = offset[0].top + el.offsetTop;
131
131
  left = offset[0].left + (spacer.parentElement ? spacer.offsetLeft : el.offsetLeft);
132
132
  if (el.tagName === 'TR') {
@@ -22,7 +22,6 @@ export let menuClass = '';
22
22
  export let menuItemClass = '';
23
23
  export let menuItemHilitedClass = '';
24
24
  export let menuItemSelectedClass = '';
25
- export let menuDividerClass = '';
26
25
  const usemenurole = true;
27
26
  </script>
28
27
 
@@ -49,4 +48,4 @@ const usemenurole = true;
49
48
  {menuItemClass}
50
49
  {menuItemHilitedClass}
51
50
  {menuItemSelectedClass}
52
- {menuDividerClass}/>
51
+ />
@@ -1,11 +1,11 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import { Store } from '@txstate-mws/svelte-store';
3
3
  import type { GlueAlignOpts, GlueAlignStore } from '../actions';
4
- import type { PopupMenuTypes } from '../types';
4
+ import type { PopupMenuItem } from '../types';
5
5
  declare const __propDef: {
6
6
  props: {
7
7
  buttonelement: HTMLButtonElement;
8
- items?: PopupMenuTypes[];
8
+ items?: PopupMenuItem[];
9
9
  menushown?: boolean;
10
10
  value?: string | undefined;
11
11
  align?: GlueAlignOpts;
@@ -25,7 +25,6 @@ declare const __propDef: {
25
25
  menuItemClass?: string;
26
26
  menuItemHilitedClass?: string;
27
27
  menuItemSelectedClass?: string;
28
- menuDividerClass?: string;
29
28
  };
30
29
  events: {
31
30
  [evt: string]: CustomEvent<any>;
@@ -22,7 +22,8 @@ setContext(CARDLAYOUT, {
22
22
  blocks.splice(blocks.indexOf(block), 1);
23
23
  hardrecalc();
24
24
  });
25
- block.order = writable(defaultOrder++);
25
+ block.order = writable(defaultOrder);
26
+ defaultOrder += 1;
26
27
  block.linebreak = writable(false);
27
28
  const cols = savecolumns || Math.ceil(1024 / maxwidth);
28
29
  block.width = writable(`calc(${100.0 / cols}% - ${gutter * (cols - 1) / cols}px)`);
@@ -63,7 +64,7 @@ async function recalculate(realw) {
63
64
  const cycling = detectcycle(realw);
64
65
  if (columns !== savecolumns) {
65
66
  for (const block of blocks)
66
- block.width.set(`calc(${100.0 / columns}% - ${guttereach}px`);
67
+ block.width.set(`calc(${100.0 / columns}% - ${guttereach}px)`);
67
68
  await tick();
68
69
  }
69
70
  // collect all the card heights at this new column width
@@ -93,11 +94,10 @@ async function recalculate(realw) {
93
94
  tallestcol = colheight;
94
95
  tallestcolidx = colidx;
95
96
  }
96
- colidx++;
97
+ colidx += 1;
97
98
  colheight = 0;
98
99
  }
99
- if (!arrangement[colidx])
100
- arrangement[colidx] = [];
100
+ arrangement[colidx] ??= [];
101
101
  arrangement[colidx].push(block);
102
102
  colheight += block.height + gutter;
103
103
  }
@@ -117,7 +117,7 @@ async function recalculate(realw) {
117
117
  }
118
118
  else {
119
119
  optimal = [];
120
- const heights = Array.apply(null, Array(columns)).map((_) => 0); // initializes heights to an array of zeroes
120
+ const heights = [...Array(columns)].map((_) => 0); // initializes heights to an array of zeroes
121
121
  // begin sorting blocks into columns
122
122
  for (const block of blocks) {
123
123
  // find the column with the smallest current height
@@ -125,8 +125,7 @@ async function recalculate(realw) {
125
125
  // record the height we are adding to the chosen column
126
126
  heights[colidx] += block.height + gutter;
127
127
  // move the current card to the chosen column
128
- if (!optimal[colidx])
129
- optimal[colidx] = [];
128
+ optimal[colidx] ??= [];
130
129
  optimal[colidx].push(block);
131
130
  }
132
131
  }
@@ -136,7 +135,8 @@ async function recalculate(realw) {
136
135
  for (let i = 0; i < optimal.length; i++) {
137
136
  for (let j = 0; j < optimal[i].length; j++) {
138
137
  const block = optimal[i][j];
139
- block.order.set(order++);
138
+ block.order.set(order);
139
+ order += 1;
140
140
  block.linebreak.set(j === optimal[i].length - 1 && i < optimal.length - 1);
141
141
  }
142
142
  }
@@ -35,7 +35,6 @@ $: passthrough = (({ condition, component, a, span, element, ...passthrough }) =
35
35
  <svelte:component this={component} bind:element {...passthrough}><slot></slot></svelte:component>
36
36
  {:else}
37
37
  {#if a}
38
- <!-- svelte-ignore a11y-missing-attribute -->
39
38
  <a bind:this={element} {...passthrough}><slot></slot></a>
40
39
  {:else if span}
41
40
  <span bind:this={element} {...passthrough}><slot></slot></span>
@@ -3,7 +3,7 @@ declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
5
  condition: boolean | undefined;
6
- component?: any | undefined;
6
+ component?: any;
7
7
  a?: boolean | undefined;
8
8
  span?: boolean | undefined;
9
9
  element?: HTMLElement | undefined;
@@ -13,7 +13,7 @@ When they do, the escape event will be fired, allowing you to remove the dialog.
13
13
  When it goes away, they'll be trapped inside the previous `FocusLock`, and so on.
14
14
  -->
15
15
  <script context="module" lang="ts">export const FocusLockStack = [];
16
- const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve) => requestAnimationFrame(resolve) : resolve => resolve(0);
16
+ const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve) => requestAnimationFrame(resolve) : (resolve) => resolve(0);
17
17
  </script>
18
18
 
19
19
  <script lang="ts">import { onMount, onDestroy, createEventDispatcher } from 'svelte';
@@ -48,7 +48,7 @@ onMount(async () => {
48
48
  pause: () => { state = 'paused'; },
49
49
  unpause: () => { if (state === 'paused')
50
50
  state = 'active'; },
51
- update: () => dispatch('focuslockupdate')
51
+ update: () => { dispatch('focuslockupdate'); }
52
52
  });
53
53
  for (const entry of FocusLockStack)
54
54
  entry.update();
@@ -123,7 +123,7 @@ const windowclick = (e) => {
123
123
  };
124
124
  </script>
125
125
  <svelte:window on:mousedown={windowclick} />
126
- <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
126
+ <!-- svelte-ignore a11y_interactive_supports_focus -->
127
127
  <div class={className} role="alertdialog" aria-modal="true" on:click|stopPropagation on:mousedown|stopPropagation on:keydown|stopPropagation={keydown} on:focusin={focusin}>
128
128
  <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
129
129
  <div bind:this={abovelockelement} tabindex="0"></div>
@@ -19,7 +19,7 @@ declare const __propDef: {
19
19
  focusId?: string;
20
20
  };
21
21
  events: {
22
- click: MouseEvent;
22
+ click: PointerEvent;
23
23
  mousedown: MouseEvent;
24
24
  escape: CustomEvent<any>;
25
25
  focuslockupdate: CustomEvent<any>;
@@ -41,7 +41,6 @@ onMount(() => {
41
41
  });
42
42
  </script>
43
43
 
44
- <!-- svelte-ignore a11y-click-events-have-key-events -->
45
44
  <!-- svelte-ignore a11y-no-static-element-interactions -->
46
45
  <div use:portal={usePortal} class="modal-backdrop" style:--modal-z={stackPosition * 10 + 3000} class:opaque on:mousedown|stopPropagation|preventDefault={() => { escapable && endmodal() }}>
47
46
  <FocusLock bind:focusId class="modal-container {containerClass}" {includeselector} {escapable} on:escape {hidefocus} {hidefocuslabel} {returnfocusto} {initialfocus} on:focuslockupdate={onFocusLockUpdate}>
@@ -47,7 +47,7 @@ export let includeDeleteAll = false;
47
47
  export let confirmDelete = undefined;
48
48
  /** A function that can be used to customize the labels on the selected item pills. Default is a function
49
49
  * that takes a PopupMenuItem and returns its label, if available, or its value when no label is provided. */
50
- export let selectedItemLabel = (item) => item.label || item.value;
50
+ export let selectedItemLabel = item => item.label || item.value;
51
51
  let menushown;
52
52
  let loading = false;
53
53
  let options = [];
@@ -58,9 +58,7 @@ let menuid;
58
58
  let announcement = '';
59
59
  const instructionid = randomid();
60
60
  const dispatch = createEventDispatcher();
61
- const optionsCache = new Cache(async (ipt) => {
62
- return await getOptions(ipt);
63
- }, { freshseconds: 5 });
61
+ const optionsCache = new Cache(async (ipt) => await getOptions(ipt), { freshseconds: 5 });
64
62
  const selectedStore = new SafeStore(selected);
65
63
  $: $selectedStore = selected;
66
64
  $: selectedSet = new Set($selectedStore.map(s => s.value));
@@ -142,6 +140,7 @@ function inputfocus() {
142
140
  }
143
141
  function inputblur() {
144
142
  active = false;
143
+ hilitedpill = undefined;
145
144
  dispatch('blur');
146
145
  }
147
146
  function removeAllSelections() {
@@ -150,7 +149,8 @@ function removeAllSelections() {
150
149
  }
151
150
  function confirmRemoveAllSelections() {
152
151
  if (confirmDelete) {
153
- if (confirm(confirmDelete)) {
152
+ // eslint-disable-next-line no-alert -- just doing something super simple here
153
+ if (window.confirm(confirmDelete)) {
154
154
  removeAllSelections();
155
155
  }
156
156
  }
@@ -177,7 +177,7 @@ let gap = 0;
177
177
  onMount(() => {
178
178
  const ul = inputelement.closest('.multiselect-selected');
179
179
  if (ul != null)
180
- gap = ((ul.offsetHeight - ul.clientHeight) / 2) + parseInt(window.getComputedStyle(ul).paddingBottom);
180
+ gap = ((ul.offsetHeight - ul.clientHeight) / 2) + parseInt(window.getComputedStyle(ul).paddingBottom, 10);
181
181
  });
182
182
  </script>
183
183
 
@@ -203,7 +203,6 @@ onMount(() => {
203
203
  {/each}
204
204
  </div>
205
205
  {/if}
206
- <!-- svelte-ignore a11y-role-has-required-aria-props -->
207
206
  <input type="text" class={inputClass} {id} {name} {disabled} {placeholder}
208
207
  bind:this={inputelement} on:input={reactToInput} on:blur={inputblur}
209
208
  on:focus={inputfocus} on:keydown={inputkeydown}
@@ -2,7 +2,7 @@
2
2
  The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
3
3
  bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
4
4
  -->
5
- <script lang="ts">import { createEventDispatcher, onDestroy, tick } from 'svelte';
5
+ <script lang="ts" generics="MenuItem extends PopupMenuItem = PopupMenuItem">import { createEventDispatcher, onDestroy, tick } from 'svelte';
6
6
  import { groupby, isNull, randomid } from 'txstate-utils';
7
7
  import { Store } from '@txstate-mws/svelte-store';
8
8
  import { glue, portal } from '../actions';
@@ -129,7 +129,7 @@ function move(idx) {
129
129
  return;
130
130
  hilited = Math.max(firstactive, Math.min(lastactive, idx));
131
131
  itemelements[hilited].scrollIntoView({ block: 'center' });
132
- buttonelement.setAttribute('aria-activedescendant', `${menuid}-${hilited}`);
132
+ buttonelement?.setAttribute('aria-activedescendant', `${menuid}-${hilited}`);
133
133
  }
134
134
  function isSelectable(itm) {
135
135
  return 'value' in itm && !itm.disabled && !hiddenItem(itm);
@@ -142,7 +142,7 @@ function onkeydown(e) {
142
142
  if (menushown) {
143
143
  let i = (hilited ?? firstactive - 1) + 1;
144
144
  while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
145
- i++;
145
+ i += 1;
146
146
  move(i);
147
147
  }
148
148
  else {
@@ -155,7 +155,7 @@ function onkeydown(e) {
155
155
  if (menushown) {
156
156
  let i = (hilited ?? lastactive + 1) - 1;
157
157
  while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
158
- i--;
158
+ i -= 1;
159
159
  move(i);
160
160
  }
161
161
  else {
@@ -199,20 +199,16 @@ function onkeydown(e) {
199
199
  value = (itemsOrderedByGroup[hilited])?.value;
200
200
  dispatch('change', itemsOrderedByGroup[hilited]);
201
201
  }
202
- else {
203
- if (buttonelement.tagName !== 'INPUT') {
204
- e.preventDefault();
205
- menushown = false;
206
- }
207
- }
208
- }
209
- else {
210
- if (buttonelement.tagName !== 'INPUT') {
202
+ else if (buttonelement?.tagName !== 'INPUT') {
211
203
  e.preventDefault();
212
- menushown = true;
213
- tick().then(() => { move(firstactive); }).catch(console.error);
204
+ menushown = false;
214
205
  }
215
206
  }
207
+ else if (buttonelement?.tagName !== 'INPUT') {
208
+ e.preventDefault();
209
+ menushown = true;
210
+ tick().then(() => { move(firstactive); }).catch(console.error);
211
+ }
216
212
  }
217
213
  else if (menushown && e.key === 'Escape') {
218
214
  e.preventDefault();
@@ -272,7 +268,7 @@ function reactToButtonElement(buttonelement) {
272
268
  buttonelement.addEventListener('input', oninput);
273
269
  const parents = getScrollParents(buttonelement);
274
270
  for (const parent of [...parents, undefined]) {
275
- const observer = new IntersectionObserver((entries) => {
271
+ const observer = new IntersectionObserver(entries => {
276
272
  for (const entry of entries) {
277
273
  if (entry.intersectionRatio < 0.1)
278
274
  menushown = false;
@@ -297,9 +293,9 @@ $: hasSelected = showSelected && !hideSelectedIndicator && items.some(itm => 'va
297
293
  </script>
298
294
 
299
295
  {#if menushown && !loading && (hasMeaningfulItems || !hideEmptyText)}
300
- <div use:portal={usePortal === true ? undefined : (usePortal || null)} use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }} class={menuContainerClass}>
296
+ <div use:portal={usePortal === true ? undefined : (usePortal ?? null)} use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }} class={menuContainerClass}>
301
297
  <ul bind:this={menuelement} id={menuid} role={usemenurole ? 'menu' : 'listbox'} tabindex="-1" style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
302
- {#each Object.entries(grouped) as [group, groupitems]}
298
+ {#each Object.entries(grouped) as [group, groupitems] (group)}
303
299
  {#if group === 'default-group'}
304
300
  {#each groupitems as item, i (item.value)}
305
301
  {@const itemIndex = getItemIndex(item)}
@@ -2,12 +2,12 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import { Store } from '@txstate-mws/svelte-store';
3
3
  import type { GlueAlignOpts, GlueAlignStore } from '../actions';
4
4
  import type { PopupMenuItem } from '../types';
5
- declare const __propDef: {
6
- props: {
5
+ declare class __sveltets_Render<MenuItem extends PopupMenuItem = PopupMenuItem> {
6
+ props(): {
7
7
  /** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
8
8
  button and be controlled by the button. Keyboard access will be enabled when the button has focus.
9
- This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */ buttonelement: HTMLElement;
10
- /** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?: PopupMenuItem[];
9
+ This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */ buttonelement: HTMLElement | undefined;
10
+ /** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?: MenuItem[] | undefined;
11
11
  menushown?: boolean;
12
12
  value?: string | undefined;
13
13
  /** Control where the menu will appear. Default is to use the current viewport to make a decision to
@@ -46,26 +46,26 @@ declare const __propDef: {
46
46
  hideEmptyText?: boolean;
47
47
  /** If true, the role for the popup menu will be 'menu' instead of the default 'listbox' value */ usemenurole?: boolean;
48
48
  };
49
- slots: {
49
+ events(): {
50
+ change: CustomEvent<MenuItem>;
51
+ };
52
+ slots(): {
50
53
  default: {
51
- item: PopupMenuItem;
54
+ item: MenuItem;
52
55
  label: string;
53
56
  hilited: boolean;
54
57
  selected: boolean;
55
58
  };
56
59
  noresults: {};
57
60
  };
58
- events: {
59
- change: CustomEvent<PopupMenuItem>;
60
- };
61
- };
62
- export type PopupMenuProps = typeof __propDef.props;
63
- export type PopupMenuEvents = typeof __propDef.events;
64
- export type PopupMenuSlots = typeof __propDef.slots;
61
+ }
62
+ export type PopupMenuProps<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['props']>;
63
+ export type PopupMenuEvents<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['events']>;
64
+ export type PopupMenuSlots<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['slots']>;
65
65
  /**
66
66
  * The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
67
67
  * bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
68
68
  */
69
- export default class PopupMenu extends SvelteComponentTyped<PopupMenuProps, PopupMenuEvents, PopupMenuSlots> {
69
+ export default class PopupMenu<MenuItem extends PopupMenuItem = PopupMenuItem> extends SvelteComponentTyped<PopupMenuProps<MenuItem>, PopupMenuEvents<MenuItem>, PopupMenuSlots<MenuItem>> {
70
70
  }
71
71
  export {};
@@ -1,3 +1,3 @@
1
1
  export * from './CardLayout.js';
2
- export * from './CollapsingTable.js';
3
- export * from './PopupMenu.js';
2
+ export type * from './CollapsingTable.js';
3
+ export type * from './PopupMenu.js';
@@ -1,3 +1 @@
1
1
  export * from './CardLayout.js';
2
- export * from './CollapsingTable.js';
3
- export * from './PopupMenu.js';
@@ -44,7 +44,7 @@ function getTranslate(element) {
44
44
  }
45
45
  // Can either be 2d or 3d transform
46
46
  const matrixType = matrix.includes('3d') ? '3d' : '2d';
47
- const matrixValues = matrix.match(/matrix.*\((.+)\)/)[1].split(', ');
47
+ const matrixValues = matrix.match(/matrix.*\((.+)\)/v)[1].split(', ');
48
48
  // 2d matrices have 6 values
49
49
  // Last 2 values are X and Y.
50
50
  // 2d matrices does not have Z value.
package/dist/util/util.js CHANGED
@@ -5,7 +5,6 @@ export function debounced(fn, timeout) {
5
5
  let timer;
6
6
  return (...args) => {
7
7
  clearTimeout(timer);
8
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
9
8
  timer = setTimeout(() => fn(...args), timeout);
10
9
  };
11
10
  }
@@ -25,7 +24,7 @@ export function getScrollParents(element) {
25
24
  if (excludeStaticParent && style.position === 'static') {
26
25
  continue;
27
26
  }
28
- if (/(auto|scroll|hidden)/.test(style.overflow + style.overflowY + style.overflowX))
27
+ if (/(auto|scroll|hidden)/v.test(style.overflow + style.overflowY + style.overflowX))
29
28
  return [parent, ...getScrollParents(parent)];
30
29
  }
31
30
  return [document.body];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@txstate-mws/svelte-components",
3
- "version": "1.6.11",
3
+ "version": "1.6.12",
4
4
  "description": "Svelte components that are generically useful.",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
@@ -10,7 +10,7 @@
10
10
  "preview": "vite preview",
11
11
  "check": "svelte-check --tsconfig ./tsconfig.json",
12
12
  "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
13
- "lint": "eslint --ignore-path .gitignore .",
13
+ "lint": "eslint .",
14
14
  "test": "playwright test tests"
15
15
  },
16
16
  "dependencies": {
@@ -20,16 +20,18 @@
20
20
  "txstate-utils": "^1.7.12"
21
21
  },
22
22
  "devDependencies": {
23
+ "@eslint/compat": "^2.0.3",
23
24
  "@playwright/test": "^1.18.1",
24
- "@sveltejs/adapter-auto": "^3.0.0",
25
+ "@stylistic/eslint-plugin": "^2.10.1",
26
+ "@sveltejs/adapter-auto": "^7.0.0",
25
27
  "@sveltejs/kit": "^2.0.0",
26
28
  "@sveltejs/package": "^2.0.2",
27
- "@types/tabbable": "^3.1.2",
28
- "eslint-config-standard-with-typescript": "^43.0.0",
29
- "eslint-plugin-svelte": "^2.0.0",
30
- "svelte-check": "^3.0.1",
31
- "svelte-preprocess": "^5.0.0",
32
- "svelte2tsx": "^0.7.0",
29
+ "@types/tabbable": "^5.0.0",
30
+ "eslint-config-love": "^151.0.0",
31
+ "eslint-plugin-svelte": "^3.0.0",
32
+ "svelte": "^5.0.0",
33
+ "svelte-check": "^4.0.0",
34
+ "svelte-preprocess": "^6.0.0",
33
35
  "tslib": "^2.3.1",
34
36
  "typescript": "^5.0.4"
35
37
  },
@@ -46,7 +48,9 @@
46
48
  },
47
49
  "./package.json": "./package.json"
48
50
  },
49
- "files": ["dist"],
51
+ "files": [
52
+ "dist"
53
+ ],
50
54
  "repository": {
51
55
  "type": "git",
52
56
  "url": "git@github.com:txstate-etc/svelte-components.git"