@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
@@ -1,174 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher } from 'svelte'
3
- import { randomid } from 'txstate-utils'
4
- import ScreenReaderOnly from './ScreenReaderOnly.svelte'
5
- import DefaultPopupMenu from './PopupMenu.svelte'
6
- import { debouncedPromise, modifierKey, selectionIsLeft } from '$lib/util'
7
- import type { PopupMenuItem } from '$lib/types'
8
-
9
- export let id = randomid()
10
- export let name: string
11
- export let getOptions: (search: string) => Promise<PopupMenuItem[]>|PopupMenuItem[]
12
- export let menuContainerClass: string|undefined = undefined
13
- export let menuClass: string|undefined = undefined
14
- export let menuItemClass: string|undefined = undefined
15
- export let menuItemHilitedClass: string|undefined = undefined
16
- export let selected: { value: string, label: string }[] = []
17
- export let placeholder = ''
18
- export let emptyText: string|undefined = undefined
19
- export let PopupMenu = DefaultPopupMenu
20
-
21
- let menushown: boolean
22
- let options = []
23
- let hilitedpill: string|undefined
24
- let inputvalue = ''
25
- let popupvalue = undefined
26
- let inputelement: HTMLInputElement
27
- const descriptionid = randomid()
28
-
29
- $: debouncedGetOptions = debouncedPromise(getOptions, 100, [])
30
- $: selectedSet = new Set(selected.map(s => s.value))
31
- async function reactToInput (..._: any) {
32
- const saveval = inputvalue
33
- const rawOptions = await debouncedGetOptions(saveval)
34
- if (inputvalue !== saveval) return // ignore any results that are out of date
35
- options = rawOptions.filter(o => !selectedSet.has(o.value))
36
- if (typeof document !== 'undefined' && inputelement === document.activeElement) menushown = true
37
- }
38
- $: reactToInput(inputvalue, debouncedGetOptions, selectedSet)
39
- $: availablemessage = options.filter(o => o.value).length + ' autocomplete choices available'
40
-
41
- function addSelection (e: CustomEvent & { detail: PopupMenuItem }) {
42
- inputvalue = ''
43
- const opt = { ...e.detail, label: e.detail.label || e.detail.value }
44
- selected = [...selected, opt]
45
- popupvalue = undefined
46
- }
47
- function removeSelection (opt: typeof selected[number], idx: number, nextfocus = 1) {
48
- if (hilitedpill === opt.value) {
49
- hilitedpill = selected[idx + nextfocus]?.value
50
- }
51
- selected = selected.filter(s => s.value !== opt.value)
52
- }
53
-
54
- function inputkeydown (e) {
55
- if (modifierKey(e)) return
56
- if (e.key === 'ArrowLeft' && selectionIsLeft(inputelement)) {
57
- const idx = hilitedpill ? selected.findIndex(s => s.value === hilitedpill) : selected.length
58
- if (idx !== 0) hilitedpill = selected[idx - 1].value
59
- } else if (e.key === 'ArrowRight' && selectionIsLeft(inputelement)) {
60
- if (hilitedpill) e.preventDefault()
61
- const idx = hilitedpill ? selected.findIndex(s => s.value === hilitedpill) : undefined
62
- if (idx != null) {
63
- if (idx === selected.length - 1) hilitedpill = undefined
64
- else hilitedpill = selected[idx + 1].value
65
- }
66
- } else if (['Backspace', 'Delete', 'Clear', ' ', 'Enter'].includes(e.key)) {
67
- if (hilitedpill) {
68
- const idx = selected.findIndex(s => s.value === hilitedpill)
69
- removeSelection(selected[idx], idx, e.key === 'Delete' ? 1 : -1)
70
- e.preventDefault()
71
- } else if (e.key === 'Backspace' && selectionIsLeft(inputelement)) {
72
- removeSelection(selected[selected.length - 1], selected.length - 1, -1)
73
- e.preventDefault()
74
- }
75
- }
76
- }
77
- function inputfocus () {
78
- reactToInput()
79
- }
80
-
81
- let popuphilited
82
- $: if (popuphilited != null) hilitedpill = undefined
83
- function reactToHilite (..._: any) {
84
- if (!inputelement) return
85
- if (hilitedpill) {
86
- popuphilited = undefined
87
- inputelement.setAttribute('aria-activedescendant', id + hilitedpill)
88
- } else inputelement.removeAttribute('aria-activedescendant')
89
- }
90
- $: reactToHilite(hilitedpill, id)
91
-
92
- const dispatch = createEventDispatcher()
93
- $: dispatch('change', selected)
94
- </script>
95
-
96
- <fieldset>
97
- <ul class="multiselect-selected" role="listbox" on:click={() => inputelement.focus()}>
98
- {#each selected as option, i}
99
- <li id={id + option.value} role="option" tabindex="-1" class="multiselect-pill" class:hilited={hilitedpill === option.value}
100
- on:click|preventDefault|stopPropagation={() => removeSelection(option, i, 1)}
101
- aria-selected="true">
102
- {option.label || option.value}
103
- <ScreenReaderOnly>, click to remove</ScreenReaderOnly>
104
- </li>
105
- {/each}
106
- <li class="input">
107
- <input type="text" id={id} name={name} placeholder={placeholder}
108
- bind:this={inputelement} bind:value={inputvalue}
109
- on:focus={inputfocus} on:keydown={inputkeydown}
110
- autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list"
111
- aria-describedby="{descriptionid}">
112
- </li>
113
- </ul>
114
- <ScreenReaderOnly id={descriptionid} arialive="assertive">
115
- <span>{selected.length ? selected.length + ' selected' : 'select multiple, none selected'}, up down to choose, left right to hilite existing choices</span>
116
- {#if menushown}<span>{availablemessage}, touch users explore to find autocomplete menu</span>{/if}
117
- </ScreenReaderOnly>
118
- <slot></slot>
119
- </fieldset>
120
- <svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft' {emptyText} {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} items={options} buttonelement={inputelement} on:change={addSelection}></svelte:component>
121
-
122
- <style>
123
- fieldset {
124
- border: 0;
125
- padding: 0;
126
- margin: 0;
127
- }
128
- fieldset * {
129
- box-sizing: border-box;
130
- }
131
- .multiselect-selected {
132
- margin: 0;
133
- padding: 0;
134
- list-style: none;
135
- display: flex;
136
- flex-wrap: wrap;
137
- align-items: stretch;
138
- padding: var(--multiselect-padding, 0.3em);
139
- border: var(--multiselect-border, 1px solid #666666);
140
- border-radius: var(--multiselect-radius, 0.3em);
141
- }
142
- .multiselect-selected:focus-within {
143
- outline-width: var(--multiselect-focus-width, 2px);
144
- outline-style: solid;
145
- outline-color: var(--multiselect-focus-color, Highlight);
146
- }
147
- li.input {
148
- flex-grow: 1;
149
- }
150
- input {
151
- outline: 0;
152
- border: 0;
153
- min-width: 0;
154
- width: 100%;
155
- height: 100%;
156
- }
157
- .multiselect-pill {
158
- cursor: pointer;
159
- flex-grow: 0;
160
- margin-right: 0.3em;
161
- line-height: 1;
162
- border-radius: var(--multiselect-pill-radius, 0.8em);
163
- border: var(--multiselect-pill-border, 1px solid gray);
164
- background-color: var(--multiselect-pill-bg, transparent);
165
- color: var(--multiselect-pill-text, black);
166
- padding: var(--multiselect-pill-padding, 0.3em 0.5em);
167
- }
168
- .multiselect-pill.hilited {
169
- outline: 0;
170
- background-color: var(--multiselect-pill-selected, gray);
171
- border: var(--multiselect-pill-selected-border, 1px solid transparent);
172
- color: var(--multiselect-pill-selected-text, white);
173
- }
174
- </style>
@@ -1,285 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher, onDestroy, tick } from 'svelte'
3
- import { randomid } from 'txstate-utils'
4
- import { Store } from '@txstate-mws/svelte-store'
5
- import { glue, portal } from '$lib/actions'
6
- import type { GlueAlignOpts, GlueAlignStore } from '$lib/actions'
7
- import type { PopupMenuItem } from '$lib/types'
8
- import ScreenReaderOnly from './ScreenReaderOnly.svelte'
9
- import { modifierKey } from '$lib/util'
10
- const dispatch = createEventDispatcher()
11
-
12
- export let menushown = false
13
- export let menuContainerClass = ''
14
- export let menuClass = ''
15
- export let menuItemClass = ''
16
- export let menuItemHilitedClass = ''
17
- export let menuItemSelectedClass = ''
18
- export let items: PopupMenuItem[] = []
19
- export let buttonelement: HTMLElement
20
- export let align: GlueAlignOpts = 'auto'
21
- export let cover = false
22
- export let showSelected = true
23
- export let width:string|undefined = undefined
24
- export let computedalign = new Store<GlueAlignStore>({ valign: 'bottom', halign: 'left' })
25
- export let usePortal: HTMLElement|true|undefined = undefined
26
- export let emptyText: string|undefined = undefined
27
- export let value: string|undefined = undefined
28
- export let hilited: number|undefined = undefined
29
- let menuelement: HTMLElement|undefined
30
- const itemelements: HTMLElement[] = []
31
- const menuid = randomid()
32
- let firstactive = 0
33
- let lastactive = items.length - 1
34
-
35
- function hiddenItem (item: PopupMenuItem) {
36
- return !!item && !showSelected && item.value === value
37
- }
38
-
39
- async function reactToItems (..._: any[]) {
40
- firstactive = items.findIndex(itm => !itm.disabled && !hiddenItem(itm))
41
- lastactive = items.length - [...items].reverse().findIndex(itm => !itm.disabled && !hiddenItem(itm)) - 1
42
- if (hilited && items[hilited]?.disabled) hilited = firstactive
43
- }
44
- $: reactToItems(items, value)
45
-
46
- async function reactToMenuShown (_: boolean) {
47
- if (!buttonelement) {
48
- hilited = undefined
49
- return
50
- }
51
- await tick()
52
- if (!menushown) {
53
- buttonelement.removeAttribute('aria-controls')
54
- buttonelement.setAttribute('aria-expanded', 'false')
55
- if (hilited) buttonelement.removeAttribute('aria-activedescendant')
56
- hilited = undefined
57
- } else {
58
- buttonelement.setAttribute('aria-controls', menuid)
59
- buttonelement.setAttribute('aria-expanded', 'true')
60
- buttonelement.focus()
61
- }
62
- }
63
- $: reactToMenuShown(menushown)
64
-
65
- function move (idx: number) {
66
- if (!menushown) return
67
- if (items[idx]?.disabled) return
68
- hilited = Math.max(firstactive, Math.min(lastactive, idx))
69
- itemelements[hilited].scrollIntoView({ block: 'center' })
70
- buttonelement.setAttribute('aria-activedescendant', `${menuid}-${hilited}`)
71
- }
72
-
73
- function onkeydown (e: KeyboardEvent) {
74
- if (modifierKey(e)) return
75
- if (e.key === 'ArrowDown') {
76
- e.preventDefault()
77
- if (menushown) {
78
- let i = (hilited ?? firstactive - 1) + 1
79
- while (items[i]?.disabled || hiddenItem(items[i])) i++
80
- move(i)
81
- } else {
82
- menushown = true
83
- tick().then(() => move(firstactive))
84
- }
85
- } else if (e.key === 'ArrowUp') {
86
- e.preventDefault()
87
- if (menushown) {
88
- let i = (hilited ?? lastactive + 1) - 1
89
- while (items[i]?.disabled || hiddenItem(items[i])) i--
90
- move(i)
91
- } else {
92
- menushown = true
93
- tick().then(() => move(lastactive))
94
- }
95
- } else if (e.key === 'Enter') {
96
- e.preventDefault()
97
- if (menushown) {
98
- menushown = false
99
- if (typeof hilited !== 'undefined') {
100
- value = items[hilited]?.value
101
- dispatch('change', items[hilited])
102
- }
103
- } else {
104
- menushown = true
105
- tick().then(() => move(firstactive))
106
- }
107
- } else if (e.key === ' ') {
108
- // buttonelement might be a text input if this popup is searchable,
109
- // so we want to allow spaces to be entered without changing the menu situation
110
- if (menushown) {
111
- if (typeof hilited !== 'undefined') {
112
- e.preventDefault()
113
- menushown = false
114
- value = items[hilited]?.value
115
- dispatch('change', items[hilited])
116
- } else {
117
- if (buttonelement.tagName !== 'INPUT') {
118
- e.preventDefault()
119
- menushown = false
120
- }
121
- }
122
- } else {
123
- if (buttonelement.tagName !== 'INPUT') {
124
- e.preventDefault()
125
- menushown = true
126
- tick().then(() => move(firstactive))
127
- }
128
- }
129
- } else if (menushown && e.key === 'Escape') {
130
- e.preventDefault()
131
- e.stopPropagation()
132
- menushown = false
133
- }
134
- }
135
-
136
- let blurTimer: any
137
- function onbuttonclick (e: MouseEvent) {
138
- e.preventDefault()
139
- cancelAnimationFrame(blurTimer)
140
- menushown = !menushown
141
- }
142
-
143
- async function onblur (e: FocusEvent) {
144
- // tabindex=-1 on our menu elements means e.relatedTarget will be set
145
- if (!(e.relatedTarget instanceof HTMLElement && menuelement?.contains(e.relatedTarget))) {
146
- blurTimer = requestAnimationFrame(() => { menushown = false })
147
- }
148
- }
149
-
150
- function cleanup (element: HTMLElement) {
151
- if (element) {
152
- element.removeEventListener('click', onbuttonclick)
153
- element.removeEventListener('keydown', onkeydown)
154
- element.removeEventListener('blur', onblur)
155
- element.removeAttribute('aria-disabled')
156
- element.removeAttribute('aria-haspopup')
157
- element.removeAttribute('aria-expanded')
158
- element.removeAttribute('aria-controls')
159
- element.removeAttribute('aria-activedescendant')
160
- }
161
- }
162
-
163
- onDestroy(() => cleanup(buttonelement))
164
-
165
- // if buttonelement changes we need to handle listeners and aria
166
- let lastbuttonelement: HTMLElement
167
- function reactToButtonElement (buttonelement: HTMLElement) {
168
- cleanup(lastbuttonelement)
169
- lastbuttonelement = buttonelement
170
- if (buttonelement) {
171
- buttonelement.setAttribute('aria-haspopup', 'listbox')
172
- buttonelement.addEventListener('click', onbuttonclick)
173
- buttonelement.addEventListener('keydown', onkeydown)
174
- buttonelement.addEventListener('blur', onblur)
175
- }
176
- }
177
- $: reactToButtonElement(buttonelement)
178
-
179
- const onclick = (item: PopupMenuItem) => (e: MouseEvent) => {
180
- e.stopPropagation()
181
- e.preventDefault()
182
- if (item.disabled) return
183
- menushown = false
184
- value = item.value
185
- dispatch('change', item)
186
- }
187
-
188
- $: hasSelected = showSelected && items.some(itm => itm.value === value)
189
- </script>
190
-
191
- {#if menushown}
192
- <div use:portal={usePortal === true ? document.body : (usePortal || null)} use:glue={{ target: buttonelement, align, cover, store: computedalign }} class={menuContainerClass}>
193
- <ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
194
- {#each items as item, i (item.value)}
195
- {#if showSelected || item.value !== value}
196
- <li
197
- id={`${menuid}-${i}`}
198
- bind:this={itemelements[i]}
199
- class={`${menuItemClass} ${i === hilited ? menuItemHilitedClass || '' : ''} ${value === item.value ? menuItemSelectedClass || '' : ''}`}
200
- class:disabled={!!item.disabled}
201
- class:hilited={!menuItemHilitedClass && i === hilited}
202
- class:selected={showSelected && !menuItemSelectedClass && value === item.value}
203
- on:click={onclick(item)}
204
- role="option"
205
- tabindex=-1
206
- aria-disabled={item.disabled}
207
- ><slot {item} label={item.label || item.value} hilited={i === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
208
- {/if}
209
- {/each}
210
- {#if items.length === 0}
211
- <li role="option" class={`${menuItemClass} disabled`} aria-live="assertive">
212
- <slot name="noresults">
213
- {#if !emptyText}
214
- <span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'empty popup menu'}</ScreenReaderOnly>
215
- {:else}
216
- {emptyText}
217
- {/if}
218
- </slot>
219
- </li>
220
- {/if}
221
- </ul>
222
- </div>
223
- {/if}
224
-
225
- <style>
226
- div {
227
- position: absolute !important;
228
- }
229
- ul {
230
- list-style: none;
231
- }
232
- ul.defaultmenu {
233
- margin: 0;
234
- padding: 0.4em;
235
- background: white;
236
- border: 1px solid slategray;
237
- border-radius: 3px;
238
- min-width: 10em;
239
- max-height: 20em;
240
- overflow-y: auto;
241
- }
242
- ul.defaultmenu li.disabled {
243
- color: rgba(0,0,0,0.6);
244
- }
245
- ul.defaultmenu::-webkit-scrollbar {
246
- appearance: none;
247
- width: 7px;
248
- }
249
- ul.defaultmenu::-webkit-scrollbar-thumb {
250
- border-radius: 4px;
251
- background-color: rgba(0,0,0,.5);
252
- box-shadow: 0 0 1px rgba(255,255,255,.5);
253
- }
254
- ul.defaultmenu::-webkit-scrollbar-track {
255
- background-color: rgba(0,0,0,.1);
256
- }
257
- li {
258
- cursor: pointer;
259
- }
260
- li.hilited {
261
- background: lightblue;
262
- }
263
- li.disabled {
264
- cursor: auto;
265
- }
266
- li.selected {
267
- position: relative;
268
- }
269
- ul.hasSelected li {
270
- padding-left: 1.4em;
271
- }
272
- li.selected:after {
273
- content: ' ';
274
- position: absolute;
275
- left: 0.5em;
276
- top: calc(50% - 0.11em);
277
- transform: translateY(-50%) rotate(38deg);
278
- display: block;
279
- height: 0.786em;
280
- width: 0.4286em;
281
- border-bottom: 0.2em solid;
282
- border-right: 0.2em solid;
283
- box-sizing: border-box;
284
- }
285
- </style>
@@ -1,9 +0,0 @@
1
- export { default as CardLayout } from './CardLayout.svelte'
2
- export { default as Card } from './Card.svelte'
3
- export { default as CollapsingTable } from './CollapsingTable.svelte'
4
- export { default as FocusLock } from './FocusLock.svelte'
5
- export { default as ConditionalWrapper } from './ConditionalWrapper.svelte'
6
- export { default as Modal } from './Modal.svelte'
7
- export { default as MultiSelect } from './MultiSelect.svelte'
8
- export { default as PopupMenu } from './PopupMenu.svelte'
9
- export { default as ScreenReaderOnly } from './ScreenReaderOnly.svelte'
package/src/lib/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './components/index.js'
2
- export * from './actions/index.js'
3
- export * from './types/index.js'
4
- export * from './util/index.js'
@@ -1,14 +0,0 @@
1
- import type { SettableSubject } from '@txstate-mws/svelte-store'
2
- export const CARDLAYOUT = {}
3
- export interface Block {
4
- element: HTMLElement
5
- order?: SettableSubject<number>
6
- linebreak?: SettableSubject<boolean>
7
- width?: SettableSubject<string>
8
- height?: number
9
- }
10
- export interface CardLayoutContext {
11
- registerBlock: (block: Block) => Block
12
- recalculate: () => void
13
- gutter: SettableSubject<number>
14
- }
@@ -1,8 +0,0 @@
1
- export interface CollapsingTableColumn {
2
- key: string
3
- title?: string
4
- width?: number
5
- neverhide?: boolean
6
- bodyCellClass?: string
7
- headerCellClass?: string
8
- }
@@ -1,5 +0,0 @@
1
- export interface PopupMenuItem {
2
- value: string
3
- label?: string
4
- disabled?: boolean
5
- }
@@ -1,3 +0,0 @@
1
- export * from './CardLayout.js'
2
- export * from './CollapsingTable.js'
3
- export * from './PopupMenu.js'
@@ -1,77 +0,0 @@
1
- export type ElementOffsets = Partial<ReturnType<typeof bodyOffset>>
2
-
3
- export function bodyOffset (ele: HTMLElement) {
4
- return targetOffset(ele, document.body)
5
- }
6
-
7
- export function targetOffset (ele: HTMLElement, target: HTMLElement) {
8
- const offset = targetOffsetRecursive(ele, target)
9
- return {
10
- ...offset,
11
- right: target.offsetWidth - offset.left - ele.offsetWidth,
12
- bottom: target.offsetHeight - offset.top - ele.offsetHeight
13
- }
14
- }
15
-
16
- export function sharedOffsetParent (a: HTMLElement, b: HTMLElement) {
17
- if (!a || !b) return undefined
18
- if (a.contains(b) && (a.firstElementChild as HTMLElement).offsetParent === a) return a
19
- if (b.contains(a) && (b.firstElementChild as HTMLElement).offsetParent === b) return b
20
- let c = a.offsetParent as HTMLElement
21
- while (c instanceof HTMLElement && !c.contains(b)) c = c.offsetParent as HTMLElement
22
- return c
23
- }
24
-
25
- function targetOffsetRecursive (ele: HTMLElement, target: HTMLElement, currentOffset?: { top: number, left: number }): { top: number, left: number } {
26
- const { x, y } = getTranslate(ele)
27
- const offset = {
28
- top: ele.offsetTop + (currentOffset?.top ?? 0) + y,
29
- left: ele.offsetLeft + (currentOffset?.left ?? 0) + x
30
- }
31
- if (ele.offsetParent instanceof HTMLElement && target !== ele.offsetParent && target.contains(ele.offsetParent)) return targetOffsetRecursive(ele.offsetParent, target, offset)
32
- return offset
33
- }
34
-
35
- function getTranslate (element: HTMLElement) {
36
- const style = window.getComputedStyle(element)
37
- const matrix = style.transform
38
-
39
- // No transform property. Simply return 0 values.
40
- if (matrix === 'none' || !matrix) {
41
- return {
42
- x: 0,
43
- y: 0,
44
- z: 0
45
- }
46
- }
47
-
48
- // Can either be 2d or 3d transform
49
- const matrixType = matrix.includes('3d') ? '3d' : '2d'
50
- const matrixValues = matrix.match(/matrix.*\((.+)\)/)![1].split(', ')
51
-
52
- // 2d matrices have 6 values
53
- // Last 2 values are X and Y.
54
- // 2d matrices does not have Z value.
55
- if (matrixType === '2d') {
56
- return {
57
- x: Number(matrixValues[4]),
58
- y: Number(matrixValues[5]),
59
- z: 0
60
- }
61
- }
62
-
63
- // 3d matrices have 16 values
64
- // The 13th, 14th, and 15th values are X, Y, and Z
65
- if (matrixType === '3d') {
66
- return {
67
- x: Number(matrixValues[12]),
68
- y: Number(matrixValues[13]),
69
- z: Number(matrixValues[14])
70
- }
71
- }
72
- return {
73
- x: 0,
74
- y: 0,
75
- z: 0
76
- }
77
- }
@@ -1,3 +0,0 @@
1
- export * from './bodyoffset.js'
2
- export * from './repositioned.js'
3
- export * from './util.js'
@@ -1,91 +0,0 @@
1
- import { equal, isNotNull, randomid, toArray } from 'txstate-utils'
2
- import { bodyOffset, targetOffset } from './bodyoffset.js'
3
- import type { ElementOffsets } from './bodyoffset.js'
4
-
5
- const observers = new Map<string, () => void>()
6
- let mutationobserver: MutationObserver
7
- export function watchForMutations (cb: () => void) {
8
- function watch () {
9
- for (const cb of observers.values()) cb()
10
- }
11
-
12
- if (observers.size === 0) {
13
- mutationobserver ??= new MutationObserver(watch)
14
- window.addEventListener('resize', watch, { passive: true })
15
- mutationobserver.observe(document.body, {
16
- subtree: true,
17
- childList: true,
18
- attributes: true,
19
- characterData: true
20
- })
21
- }
22
-
23
- const id = randomid()
24
- observers.set(id, cb)
25
- cb()
26
-
27
- return {
28
- destroy () {
29
- observers.delete(id)
30
- if (observers.size === 0) {
31
- window.removeEventListener('resize', watch)
32
- mutationobserver.disconnect()
33
- }
34
- }
35
- }
36
- }
37
-
38
- export function watchForPositionChange (el: (HTMLElement|undefined)[]|HTMLElement|undefined, cb: ((offset: Required<ElementOffsets>) => void)|((offsets: Required<ElementOffsets>[]) => void)) {
39
- let lastoffsets: ElementOffsets[] = []
40
- let els = toArray(el).filter(isNotNull)
41
- function watch () {
42
- const offsets = els.map(bodyOffset)
43
- if (offsets.some((offset, i) => !equal(offset, lastoffsets[i]))) {
44
- lastoffsets = offsets
45
- Array.isArray(el) ? (cb as any)(offsets) : (cb as any)(offsets[0])
46
- }
47
- }
48
-
49
- const { destroy } = watchForMutations(watch)
50
-
51
- return {
52
- update (newEl: (HTMLElement|undefined)[]|HTMLElement|undefined, newCb: ((offset: Required<ElementOffsets>) => void)|((offsets: Required<ElementOffsets>[]) => void)) {
53
- const changed = newEl !== el || newCb !== cb
54
- if (changed) {
55
- el = newEl
56
- els = toArray(el).filter(isNotNull)
57
- cb = newCb
58
- watch()
59
- }
60
- },
61
- destroy
62
- }
63
- }
64
-
65
- export function watchForPositionChangeInContainer (el: HTMLElement|undefined, container: HTMLElement|undefined, cb: (offset: Required<ElementOffsets>) => void) {
66
- let lastoffset: ElementOffsets
67
-
68
- function watch () {
69
- if (!el || !container) return
70
- const offset = targetOffset(el, container)
71
- if (!equal(offset, lastoffset)) {
72
- lastoffset = offset
73
- cb(offset)
74
- }
75
- }
76
-
77
- const { destroy } = watchForMutations(watch)
78
-
79
- return {
80
- update (newEl: HTMLElement|undefined, newContainer: HTMLElement|undefined, newCb: (offset: Required<ElementOffsets>) => void) {
81
- const changed = newEl !== el || newContainer !== container || newCb !== cb
82
- if (changed) {
83
- el = newEl
84
- container = newContainer
85
- cb = newCb
86
- watch()
87
- }
88
- },
89
- destroy
90
- }
91
- }