@greendrake/ui 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,12 +21,33 @@ There is no build step and no `.d.ts`: `exports` points at `src/main.ts`, so the
21
21
 
22
22
  ## Styling contract
23
23
 
24
- Component styles are SCSS inside the SFCs and resolve `@greendrake/scss-kit` (breakpoints, z-layers, icon masks, control mixins) at compile time. Colours, spacing and typography come from CSS custom properties: the consuming app loads `@greendrake/theme` (tokens, document styling, the global `.spinner` rules that `AsyncAction`, `AsyncSearch`, `TreeView`, `ThumbnailGrid` and `Gallery` toggle) and defines the app-provided properties listed under "CSS custom property contract" in that package's README. Properties that README leaves to this package, each read with a local default:
24
+ Component styles are SCSS inside the SFCs and resolve `@greendrake/scss-kit` (breakpoints, z-layers, icon masks, control mixins) at compile time; the custom properties those mixins read are listed in its README. Colours, spacing and typography come from CSS custom properties the consuming app defines. `AsyncAction`, `AsyncSearch`, `TreeView`, `ThumbnailGrid` and `Gallery` show progress by toggling a `spinner` class, which the app styles globally.
25
+
26
+ Read with no fallback, so the app defines them:
27
+
28
+ | Property | Read by |
29
+ | --- | --- |
30
+ | `--font-color` | text throughout: `AsyncSearch`, `BottomNav`, `Checkbox`, `Gallery`, `Modal`, `Password`, `SearchField`, `TreeView` |
31
+ | `--background-color` | `AsyncSearch` narrow panel |
32
+ | `--form-background`, `--form-background-hover` | control surfaces: `AsyncSearchResults`, `BottomNav`, `SegmentedControl`, `TabBar`, `ThumbnailGrid`, `TreeView` |
33
+ | `--input-background` | `Checkbox` box |
34
+ | `--border-color`, `--border-color-light`, `--border-color-hover` | control and container borders |
35
+ | `--border-radius` | `AsyncSearch`, `AsyncSearchResults`, `Checkbox`, `ThumbnailGrid` (`FieldTip` falls back to 4px) |
36
+ | `--accent-color` | active and checked states: `BottomNav`, `Checkbox`, `SegmentedControl`, `TabBar`, toasts |
37
+ | `--space-1` … `--space-5` | spacing |
38
+ | `--font-xs`, `--font-md`, `--font-lg` | `BottomNav` and `ThumbnailGrid` labels, `FieldTip`, `Modal` title |
39
+ | `--line-height-normal` | `Password` |
40
+ | `--modal-background`, `--modal-border-radius` | `Modal` window |
41
+
42
+ Read with a local default:
25
43
 
26
44
  | Property | Read by | Default |
27
45
  | --- | --- | --- |
28
46
  | `--checkbox-size` | `Checkbox` box | 24px, set on `.Checkbox` |
29
47
  | `--switch-width`, `--switch-height`, `--switch-knob` | `Checkbox` with `switch` | 40px / 24px / `calc(var(--switch-height) - 4px)`, set on `.Checkbox--switch` |
48
+ | `--modal-box-shadow`, `--modal-backdrop-filter`, `--modal-window-backdrop-filter`, `--modal-button-min-width` | `Modal` | per property |
49
+ | `--circular-progress-arc`, `--circular-progress-label`, `--circular-progress-track` | `CircularProgress` | per property |
50
+ | `--row-select-color`, `--tree-line-color` | `TreeView` | per property |
30
51
  | `--accent-contrast` | `SegmentedControl` active segment text | none |
31
52
  | `--font-color-muted` | `SelectBox` placeholder, `BottomNav` inactive tabs | `--vs-text-color` / `--font-color` |
32
53
  | `--bottomnav-height` | `BottomNav` bar height; bottom edge of the `AsyncSearch` narrow panel | none / 0px |
@@ -310,7 +331,7 @@ type ThumbnailGridLoader = (params: ThumbnailGridLoaderParams, loading: Ref<bool
310
331
 
311
332
  ### useSelection
312
333
 
313
- `useSelection<Id extends string | number>(items: Ref<Record<string, unknown>[]> | ComputedRef<...>, idField: string)` — flat, index-addressable multi-selection over an ordered list. Returns `{ selected: Set<Id> (shallowReactive), getId(index), indexOf(id), addRange(fromId, toId), toggle(id), replace(id), rangeTo(id) (from the anchor set by the last toggle/replace), moveCursor('up' | 'down', extend: boolean): number (returns the new cursor index), selectAll(), clear() }`. `TreeView` and `@greendrake/ui-data`'s `InfiniteScrollTable` use it.
334
+ `useSelection<Id extends string | number>(items: Ref<Record<string, unknown>[]> | ComputedRef<...>, idField: string)` — flat, index-addressable multi-selection over an ordered list. Returns `{ selected: Set<Id> (shallowReactive), getId(index), indexOf(id), addRange(fromId, toId), toggle(id), replace(id), rangeTo(id) (from the anchor set by the last toggle/replace), moveCursor('up' | 'down', extend: boolean): number (returns the new cursor index), selectAll(), clear() }`. `TreeView` uses it.
314
335
 
315
336
  ### RowSelect
316
337
 
@@ -324,4 +345,4 @@ type ThumbnailGridLoader = (params: ThumbnailGridLoaderParams, loading: Ref<bool
324
345
  - `downsizeImage(file: File, opts?: { maxDimension?: number; maxSizeKB?: number; minQuality?: number }): Promise<File>` — re-encodes an oversized image as JPEG, scaling to `maxDimension` (2000) and stepping quality down until under `maxSizeKB` (1024) or `minQuality` (0.2). Non-images, files within both limits, and undecodable files pass through untouched.
325
346
  - `scrollToTop()` — `window.scroll({ top: 0 })`.
326
347
  - `focusIn(el: string | HTMLElement | null, selector?: string)` — focuses the element with that id, or the first `selector` match inside `el`, unless it already has focus.
327
- - `TOUCH_PAN_SLOP_PX` — 10; the travel a touch must cover before it counts as a directional gesture (shared with `@greendrake/ui-data`'s table panning).
348
+ - `TOUCH_PAN_SLOP_PX` — 10; the travel a touch must cover before it counts as a directional gesture; exported so other gesture-arbitrating components share it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greendrake/ui",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Vue 3 component library: form controls, modals, search, tabs, toasts, layout",
5
5
  "license": "MIT",
6
6
  "author": "Eugene Greendrake <eugene@greendrake.nz>",
@@ -33,9 +33,9 @@
33
33
  },
34
34
  "prettier": "@greendrake/dev-config/prettier",
35
35
  "dependencies": {
36
- "@greendrake/domains": "0.3.0",
37
- "@greendrake/scss-kit": "0.3.0",
38
- "@greendrake/util": "0.3.0",
36
+ "@greendrake/domains": "0.5.0",
37
+ "@greendrake/scss-kit": "0.5.0",
38
+ "@greendrake/util": "0.5.0",
39
39
  "vue-tippy": "^6.7.1"
40
40
  },
41
41
  "peerDependencies": {
@@ -43,7 +43,7 @@
43
43
  "vue-router": "^4.6.4"
44
44
  },
45
45
  "devDependencies": {
46
- "@greendrake/dev-config": "0.3.0",
46
+ "@greendrake/dev-config": "0.5.0",
47
47
  "typescript": "6.0.3",
48
48
  "vue": "^3.5.34",
49
49
  "vue-router": "^4.6.4",
package/src/Field.vue CHANGED
@@ -12,7 +12,7 @@ defineProps<{
12
12
  // every click in the field would be FORWARDED to it as a real click, so
13
13
  // clicking into the text would silently press Bold.
14
14
  //
15
- // Such a control names itself (RichEditor's ariaLabel puts it on the
15
+ // Such a control names itself (a rich editor puts its aria-label on the
16
16
  // contenteditable), so the caption is left as plain text — no role="group",
17
17
  // which would only announce the name a second time.
18
18
  unlabelable?: boolean
package/src/SelectBox.vue CHANGED
@@ -119,6 +119,12 @@ const placeMenu = async (): Promise<void> => {
119
119
  // the visual viewport to hold a focused field clear of the keyboard.
120
120
  const vv = window.visualViewport
121
121
  const viewportBottom = vv ? vv.offsetTop + vv.height : window.innerHeight
122
+ // The ceiling is the top of what is on screen for the same reason, and lower
123
+ // still by the top safe-area inset: in a native shell the status bar
124
+ // overlays that strip and the page's content stops short of it. The token is
125
+ // optional, as everywhere it is read — undeclared, there is no inset.
126
+ const insetTop = getComputedStyle(m).getPropertyValue('--safe-area-inset-top')
127
+ const viewportTop = (vv ? vv.offsetTop : 0) + (insetTop ? parseFloat(insetTop) : 0)
122
128
  const menuRect = m.getBoundingClientRect()
123
129
  let floor = Math.min(viewportBottom, menuRect.bottom + MARGIN)
124
130
  const probe = document.elementFromPoint(Math.round(menuRect.x + menuRect.width / 2), Math.round(Math.min(menuRect.bottom, viewportBottom) - 4))
@@ -126,7 +132,7 @@ const placeMenu = async (): Promise<void> => {
126
132
  floor = Math.min(floor, probe.getBoundingClientRect().top)
127
133
  }
128
134
  const below = floor - rootRect.bottom - MARGIN
129
- const above = rootRect.top - MARGIN
135
+ const above = rootRect.top - viewportTop - MARGIN
130
136
  if (height > below && above > below) {
131
137
  menuStyle.value = { ...anchorStyle(rootRect, true), maxHeight: `${Math.min(height, above)}px` }
132
138
  } else if (height > below) {
package/src/TextField.vue CHANGED
@@ -25,8 +25,8 @@ const props = withDefaults(
25
25
  validateOnBlur?: boolean
26
26
  // The rich-text editor component rendered when type === 'rich'. It must
27
27
  // speak the same string v-model contract as this field. Supplied by the
28
- // consumer (see @greendrake/ui-rich) so tiptap is bundled only where
29
- // rich editing is actually used. Init-time contract: read once below.
28
+ // consumer so a rich-text editor is bundled only where rich editing is
29
+ // actually used. Init-time contract: read once below.
30
30
  richEditor?: Component
31
31
  }>(),
32
32
  {
package/src/touchSlop.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  // Distance a touch/pen pointer must travel before the gesture counts as a
2
2
  // deliberate directional move rather than a tap or start-of-gesture jitter —
3
- // mirrors typical platform touch slop. Its own module so the components that
4
- // arbitrate gestures (the sheet drag in Modal, the pan in InfiniteScrollTable)
5
- // share one value without importing each other.
3
+ // mirrors typical platform touch slop. Exported so every component arbitrating
4
+ // gestures — the sheet drag in Modal, a table's pan — shares one value.
6
5
  export const TOUCH_PAN_SLOP_PX = 10