@reqquest/ui 1.14.0 → 1.15.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/dist/components/QuestionnairePrompt.svelte +2 -1
- package/dist/components.md +76 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/registry.d.ts +55 -0
- package/dist/registry.js +47 -0
- package/dist/status-reasons.d.ts +19 -0
- package/dist/status-reasons.js +45 -0
- package/dist/types.d.ts +10 -7
- package/dist/types.js +3 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import Button from "carbon-components-svelte/src/Button/Button.svelte";
|
|
2
|
+
import Button from "carbon-components-svelte/src/Button/Button.svelte";
|
|
3
|
+
import Launch from 'carbon-icons-svelte/lib/Launch.svelte'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Array of external links to show above the form.
|
package/dist/components.md
CHANGED
|
@@ -10,10 +10,10 @@ Versions this describes (the versions a downstream project resolves through `@re
|
|
|
10
10
|
|
|
11
11
|
| package | version |
|
|
12
12
|
|---|---|
|
|
13
|
-
| `@reqquest/ui` | 1.
|
|
14
|
-
| `@txstate-mws/carbon-svelte` | 1.
|
|
13
|
+
| `@reqquest/ui` | 1.14.1 |
|
|
14
|
+
| `@txstate-mws/carbon-svelte` | 1.12.1 |
|
|
15
15
|
| `@txstate-mws/svelte-forms` | 2.1.9 |
|
|
16
|
-
| `carbon-components-svelte` | 0.
|
|
16
|
+
| `carbon-components-svelte` | 0.111.1 |
|
|
17
17
|
| `carbon-icons-svelte` | 13.15.0 |
|
|
18
18
|
| `svelte` | 5.x, **no runes** |
|
|
19
19
|
|
|
@@ -108,6 +108,7 @@ Both members of the pair are mounted with `<svelte:component>`, so they are unty
|
|
|
108
108
|
| `gatheredConfigData` | yes | yes | configuration gathered from related definitions. |
|
|
109
109
|
| `invalidated` / `invalidatedReason` | yes | yes | set when a reviewer sent the prompt back for correction. |
|
|
110
110
|
| `appRequestId` | **sometimes** | yes | present on the applicant page, the opt-out modal, the display path, and the `formMode: 'full'` modal - but **not** on the reviewer's inline edit form. Guard it, or register `formMode: 'full'` if you truly need it. |
|
|
111
|
+
| `statusReasons` | yes | yes | `{ status, statusReason, programName, blamed }[]` - every requirement status and reason aimed at this prompt, across programs. `blamed` is true when the requirement's `resolve` named this prompt in `blame` (it may be a requirement other than this prompt's own, e.g. a reviewer requirement blaming an applicant answer); false when it named no prompt and the entry is the fallback attached to all of that requirement's prompts. The framework already renders the **blamed** entries below every edit form as inline notifications (see `statusReasonNotifications` below), so a form component normally needs nothing. Display components receive the same list and render nothing by default; call `statusReasonsToFeedback(statusReasons, { blamedOnly, statuses })` from `@reqquest/ui` to get `Feedback` objects for `FormInlineNotification`, passing `uiRegistry.statusReasonNotificationStatuses(key, 'applicant' | 'reviewer')` as `statuses` to follow the project's `statusReasonNotifications` configuration. Applicants get an empty list for reviewer-phase requirements the API redacts. |
|
|
111
112
|
|
|
112
113
|
`prestageData` has a trap worth knowing before you hit it. Read the **prop**, not the mutation:
|
|
113
114
|
|
|
@@ -205,9 +206,28 @@ export const uiRegistry = new UIRegistry(config)
|
|
|
205
206
|
`fetched` only.
|
|
206
207
|
- **`icon`** `Component` - navigation icon.
|
|
207
208
|
- **`loader`** `{ skeletonComponent, delay? } | boolean` (default `false`) - see the snippet above.
|
|
208
|
-
- **`applicantPromptPage`** `{ formClass?, invalidatedInlineNotificationClass? }` -
|
|
209
|
-
override of the CSS applied to the framework `Form` that wraps applicant prompts
|
|
210
|
-
|
|
209
|
+
- **`applicantPromptPage`** `{ formClass?, invalidatedInlineNotificationClass?, statusReasonInlineNotificationClass? }` -
|
|
210
|
+
per-prompt override of the CSS applied to the framework `Form` that wraps applicant prompts, the
|
|
211
|
+
corrections notice, and the wrapper around the status reason notices. The global version of the
|
|
212
|
+
same setting lives on `UIConfig`.
|
|
213
|
+
- **`statusReasonNotifications`** `boolean | { <STATUS>?: boolean | { applicant?, reviewer? } }` -
|
|
214
|
+
which requirement status reasons that `blame` this prompt the framework renders as inline
|
|
215
|
+
notifications below its form component, and to whom. The audience is the UI surface, not the
|
|
216
|
+
viewer's role: `applicant` is the applicant prompt page, the accept page and the opt-out modal;
|
|
217
|
+
`reviewer` is the reviewer's inline and modal forms. Notices are styled like svelte-forms
|
|
218
|
+
validation messages, keyed by status (DISQUALIFYING error, WARNING warning, PENDING info, MET
|
|
219
|
+
success), describe the last *saved* answer and refresh after each save. A boolean switches every
|
|
220
|
+
status for both audiences; an object names statuses, each as a boolean (both audiences) or
|
|
221
|
+
`{ applicant?, reviewer? }` for one side. Resolution is layered **per status and per audience**:
|
|
222
|
+
the built-in default (everything **except PENDING**, both audiences), then
|
|
223
|
+
`UIConfig.statusReasonNotifications`, then this prompt's value - each layer changes only what it
|
|
224
|
+
names. So `UIConfig: { PENDING: { reviewer: true } }` shows PENDING instructions to reviewers only,
|
|
225
|
+
and a prompt with `{ DISQUALIFYING: { applicant: false } }` hides that one notice from the applicant
|
|
226
|
+
while reviewers and every other status follow the global choice. `false` at either level renders
|
|
227
|
+
nothing (for that prompt, or everywhere) - use the `statusReasons` prop in your own form component
|
|
228
|
+
instead. `statusReasonsToFeedback(statusReasons, { blamedOnly, statuses })` from `@reqquest/ui`
|
|
229
|
+
applies the same filters; pass `uiRegistry.statusReasonNotificationStatuses(key, audience)` as
|
|
230
|
+
`statuses` if a display component should follow the project's configuration.
|
|
211
231
|
|
|
212
232
|
### Keys, and how they stay honest
|
|
213
233
|
|
|
@@ -1757,6 +1777,20 @@ Import: `import { CardSkeleton } from '@txstate-mws/carbon-svelte'`
|
|
|
1757
1777
|
|
|
1758
1778
|
- **`width`** (default: `300`) - The width of the card in pixels
|
|
1759
1779
|
|
|
1780
|
+
### ExpandCollapseAll
|
|
1781
|
+
|
|
1782
|
+
> One ghost button that opens or closes every member of a collapsible set at once, labelled to describe what the next activation will do. `members` is the whole input: the label is derived from it, and an empty set renders nothing. `PanelGroup` and `ColumnList` each own their set and can render this already wired, so most pages never build the array themselves. The **root element is a wrapper `div`, not the Button**, and `class` is the only prop forwarded to it. That is what lets a caller position the control without colliding with carbon's button classes. In a flex row the wrapper is exactly the Button's height, so it costs no layout. @example ```svelte <script lang="ts"> import { ExpandCollapseAll, PanelGroup, Panel } from '@txstate-mws/carbon-svelte' </script> <PanelGroup let:members let:expandAll let:collapseAll> <ExpandCollapseAll class="[ flex justify-end mb-2 ]" {members} on:expand={expandAll} on:collapse={collapseAll} /> <Panel expandable title="Petition">…</Panel> <Panel expandable title="Supporting documents">…</Panel> <Panel expandable title="Advisor recommendation">…</Panel> <Panel expandable title="Course substitutions">…</Panel> <Panel expandable title="Decision history">…</Panel> </PanelGroup> ```
|
|
1783
|
+
|
|
1784
|
+
Import: `import { ExpandCollapseAll } from '@txstate-mws/carbon-svelte'`
|
|
1785
|
+
|
|
1786
|
+
<!-- no upstream doc at this version; props extracted from the component source -->
|
|
1787
|
+
|
|
1788
|
+
#### Props
|
|
1789
|
+
|
|
1790
|
+
- **`members`** `boolean[]` **(required)** - The expanded state of every member of the collapsible set, one boolean each. The label reads "Collapse all" only while every member is expanded, and an empty array renders nothing. Inside a `PanelGroup` this is the group's `let:members`; `ColumnList` builds it from its expandable rows; any other set (e.g. carbon `Accordion`, your own markup) builds it itself.
|
|
1791
|
+
- **`size`** `'small' | 'medium' | 'large'` (default: `'medium'`) - Button size.
|
|
1792
|
+
- **`describedById`** `string | undefined` (default: `undefined`) - Id of an element naming the set this button acts on, applied to the Button as `aria-describedby`.
|
|
1793
|
+
|
|
1760
1794
|
### FilterUISkeleton
|
|
1761
1795
|
|
|
1762
1796
|
Import: `import { FilterUISkeleton } from '@txstate-mws/carbon-svelte'`
|
|
@@ -1773,6 +1807,29 @@ Import: `import { GeneralTextSkeleton } from '@txstate-mws/carbon-svelte'`
|
|
|
1773
1807
|
|
|
1774
1808
|
<!-- no upstream doc at this version; props extracted from the component source -->
|
|
1775
1809
|
|
|
1810
|
+
### Image
|
|
1811
|
+
|
|
1812
|
+
> A single presentational image, rendered as exactly one img element with no wrapper. Three layout modes: `aspect` locks a ratio and cover-crops; `cropHeight` crops to a fixed CSS height at whatever width the container gives; with neither, the image renders at its intrinsic ratio, never upscaling past its natural width. `anchor` picks the visible region in both crop modes. While the file downloads the box is reserved empty space. Give intrinsic images `width` and `height` so the browser can reserve it before the file arrives. `class`, `style` and any other undeclared attribute (fetchpriority, crossorigin, id, ...) all land on the img. Purely presentational: a consumer who needs a linked image wraps it in an anchor. @example ```svelte <script lang="ts"> import { Image } from '@txstate-mws/carbon-svelte' </script> <Image src="/photo.jpg" aspect="widescreen" anchor={{ x: 75, y: 25 }} /> ``` A banner cropped to a fixed height at fluid width: ```svelte <Image src="/photo.jpg" cropHeight="180px" anchor="object-bottom" /> ``` Intrinsic ratio with the box reserved from the file's real pixel dimensions: ```svelte <Image src="/photo.jpg" width={1600} height={900} /> ```
|
|
1813
|
+
|
|
1814
|
+
Import: `import { Image } from '@txstate-mws/carbon-svelte'`
|
|
1815
|
+
|
|
1816
|
+
<!-- no upstream doc at this version; props extracted from the component source -->
|
|
1817
|
+
|
|
1818
|
+
#### Props
|
|
1819
|
+
|
|
1820
|
+
- **`src`** `string | undefined` (default: `undefined`) - Image source URL. Optional so async data can be passed directly: with `src` undefined the component renders nothing.
|
|
1821
|
+
- **`alt`** (default: `''`) - Alt text for accessibility. Defaults to empty string: images are decorative by default. When `alt` describes specific content and the image is cropped (`aspect` or `cropHeight`), choose `anchor` appropriately so cover-cropping doesn't cut away what the alt text describes.
|
|
1822
|
+
- **`srcset`** `string | undefined` (default: `undefined`) - Native srcset attribute, passed through to the img element: the same image at several widths ('/photo-400.jpg 400w, /photo-800.jpg 800w'), letting the browser download the smallest file that will still look sharp. Pairs with `sizes`.
|
|
1823
|
+
- **`sizes`** `string | undefined` (default: `undefined`) - Native `sizes` attribute. Passed through as given, but a lazy image that has a `srcset` and no `sizes` gets `'auto'` by default, so the browser picks a candidate sized to the laid-out box instead of assuming 100vw.
|
|
1824
|
+
- **`loading`** `'lazy' | 'eager'` (default: `'lazy'`) - Native loading attribute for the img element. Defaults to 'lazy': the file is not fetched until the image is about to scroll into view.
|
|
1825
|
+
- **`width`** `number | undefined` (default: `undefined`) - The image file's intrinsic width in pixels, passed through as the native `width` attribute. Set it together with `height` in intrinsic mode: the browser derives an aspect ratio from the pair and reserves the box before the file arrives, which prevents layout shift. Ignored for layout in the crop modes (`aspect` or `cropHeight`), since CSS already gives the box. These are pixel counts, not CSS lengths.
|
|
1826
|
+
- **`height`** `number | undefined` (default: `undefined`) - The image file's intrinsic height in pixels, passed through as the native `height` attribute. See `width`.
|
|
1827
|
+
- **`anchor`** `ImageAnchor` (default: `'object-center'`) - Which part of the image stays visible when it is cover-cropped: one of the nine `object-*` position shorthands, or an `{ x, y }` focal point in percentages. Live in both crop modes (`aspect` or `cropHeight`); inert in intrinsic mode. A focal point keeps its relative position in the crop (CSS `object-position` semantics); it is never pulled to the center.
|
|
1828
|
+
- **`aspect`** `ImageAspect | undefined` (default: `undefined`) - Locks the image to an aspect ratio, cropping via cover-fit: 'square' (1:1), 'widescreen' (16:9), 'standard' (4:3), any CSS ratio string like '21/9', or a bare width/height number like 1.7778. Ratio strings use the CSS slash form; a colon form like '4:5' is invalid CSS and the browser silently drops it, leaving the image uncropped. When omitted the image renders at its intrinsic ratio with no cropping, unless `cropHeight` is set.
|
|
1829
|
+
- **`cropHeight`** `string | undefined` (default: `undefined`) - Crops the image to an explicit CSS height ('180px', '50vh', '100%') at whatever width the container gives, via cover-fit, which also upscales a file smaller than the box. This is the unlocked mode, for banners and fills where the ratio can't be known ahead of time. Percentage heights resolve against the parent, which must be sized; against an unsized parent they silently behave as auto. Ignored when `aspect` is set.
|
|
1830
|
+
- **`className`** (default: `''`) - Extra classes for the img, the hook for placing and sizing the component from outside.
|
|
1831
|
+
- **`style`** `string | undefined` (default: `undefined`) - Inline styles for the img. The component's own style directives reserve `aspect-ratio`, `height` and `object-position` in every mode, so Svelte drops those properties from this string. The `aspect`, `cropHeight` and `anchor` props are the way to set them.
|
|
1832
|
+
|
|
1776
1833
|
### LoadingIcon
|
|
1777
1834
|
|
|
1778
1835
|
Import: `import { LoadingIcon } from '@txstate-mws/carbon-svelte'`
|
|
@@ -1798,6 +1855,18 @@ Import: `import { PanelDialog } from '@txstate-mws/carbon-svelte'`
|
|
|
1798
1855
|
- **`loadingSubmit`** (default: `false`) - Show a loading spinner on the submit button and disable it when true.
|
|
1799
1856
|
- **`size`** `'small' | 'large'` (default: `'small'`) - Size of the dialog. 'small' is 32rem wide, 'large' is 64rem wide and centered.
|
|
1800
1857
|
|
|
1858
|
+
### PanelGroup
|
|
1859
|
+
|
|
1860
|
+
Import: `import { PanelGroup } from '@txstate-mws/carbon-svelte'`
|
|
1861
|
+
|
|
1862
|
+
<!-- no upstream doc at this version; props extracted from the component source -->
|
|
1863
|
+
|
|
1864
|
+
#### Props
|
|
1865
|
+
|
|
1866
|
+
- **`showExpandAll`** (default: `false`) - One Panel's handle on the group. / export interface PanelMembership { /** This Panel's state, narrowed to one boolean. / expanded: Readable<boolean> /** This Panel pushing its own change up: a header click, or an app writing `expanded`. */ report: (expanded: boolean) => void /** Call on destroy. Drops the key from the mounted set and keeps its state. */ leave: () => void } export interface PanelGroupContextValue { /** Called by an expandable Panel as it mounts. Seeds state on first sight only, so a Panel remounting under the same key reads back the state it left. / join: (finalKey: string, initial: boolean) => PanelMembership } /** Module scope on purpose: an instance-scope key is a fresh object per instance. / export const PANEL_GROUP_CONTEXT = {} </script> <script lang="ts"> import { dev } from '$app/environment' import { setContext } from 'svelte' import { derived, writable } from 'svelte/store' import ExpandCollapseAll from './ExpandCollapseAll.svelte' /** Render an expand-all control above the group's Panels, right aligned. Leave it off and wire your own `ExpandCollapseAll` from the `let:` slot props when the control belongs in a toolbar you already have.
|
|
1867
|
+
- **`expandAllSize`** `'small' | 'medium' | 'large'` (default: `'medium'`) - Size of the built-in expand-all control. Only used when `showExpandAll` is set; a control you place yourself takes its own `size` prop.
|
|
1868
|
+
- **`describedById`** `string | undefined` (default: `undefined`) - Id of an element naming this group, forwarded to the built-in "Expand all" control as `aria-describedby` so a screen reader user can tell two "Expand all" buttons apart when a screen has more than one set.
|
|
1869
|
+
|
|
1801
1870
|
### ScrollOverflow
|
|
1802
1871
|
|
|
1803
1872
|
Import: `import { ScrollOverflow } from '@txstate-mws/carbon-svelte'`
|
|
@@ -3400,7 +3469,7 @@ Import: `import { FieldMultiselectPills } from '@txstate-mws/carbon-svelte'`
|
|
|
3400
3469
|
- **`id`** (default: `randomid()`)
|
|
3401
3470
|
- **`labelText`** `string` (default: `''`)
|
|
3402
3471
|
- **`placeholder`** (default: `''`) - Text to display in the text input when it's empty.
|
|
3403
|
-
- **`disabled`** (default: `
|
|
3472
|
+
- **`disabled`** `boolean | undefined` (default: `undefined`) - When there are no items (e.g. it's a filtered search and there were no results), we still display one disabled item in the menu to let the user know what is going on. Use this prop to specify the message. */ /** Indicates if the field is disabled.
|
|
3404
3473
|
- **`defaultValue`** `string[]` (default: `[]`)
|
|
3405
3474
|
- **`conditional`** `boolean | undefined` (default: `undefined`)
|
|
3406
3475
|
- **`required`** (default: `false`)
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/registry.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LayoutStructureNode, LayoutStructureNodeRoot, ShellItem, UserProfile } from '@txstate-mws/carbon-svelte';
|
|
2
2
|
import type { Component } from 'svelte';
|
|
3
3
|
import type { ProgramKey, PromptKey, RequirementKey } from './keys.js';
|
|
4
|
+
import type { RequirementStatus } from './typed-client/index.js';
|
|
4
5
|
export interface PromptLoader {
|
|
5
6
|
skeletonComponent: Component<any>;
|
|
6
7
|
/**
|
|
@@ -148,9 +149,46 @@ export interface PromptDefinition {
|
|
|
148
149
|
/**
|
|
149
150
|
* This is the same applicantPromptPage CSS configurations that exist at the UIConfig (global applicant page prompt) level. Used in a situation where a specific prompt
|
|
150
151
|
* requires a specific layout that doesn't match default. This will override global configuration
|
|
152
|
+
* (`formClass`, `invalidatedInlineNotificationClass`, `statusReasonInlineNotificationClass`).
|
|
151
153
|
*/
|
|
152
154
|
applicantPromptPage?: ApplicantPromptPageDefinition;
|
|
155
|
+
/**
|
|
156
|
+
* Which requirement status reasons the framework renders as inline notifications below this prompt's
|
|
157
|
+
* form component, and to whom. Only reasons whose requirement explicitly `blame`s this prompt are
|
|
158
|
+
* rendered. The audience is the UI surface: `applicant` is the applicant prompt page, the accept page and
|
|
159
|
+
* the opt-out modal; `reviewer` is the reviewer's inline and modal forms.
|
|
160
|
+
*
|
|
161
|
+
* A boolean switches every status for both audiences. An object names statuses; each status takes a
|
|
162
|
+
* boolean (both audiences) or `{ applicant?, reviewer? }` to set one side only, e.g.
|
|
163
|
+
* `{ PENDING: { reviewer: true }, DISQUALIFYING: { applicant: false } }`. Resolution is layered per status
|
|
164
|
+
* and per audience: the built-in default (`DEFAULT_STATUS_REASON_NOTIFICATIONS`, everything except
|
|
165
|
+
* PENDING), then the global `UIConfig.statusReasonNotifications`, then this value - each layer changes only
|
|
166
|
+
* what it names. Set false to render nothing here and handle the `statusReasons` prop inside your own form
|
|
167
|
+
* component instead.
|
|
168
|
+
*/
|
|
169
|
+
statusReasonNotifications?: StatusReasonNotificationConfig;
|
|
153
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Who sees a status's reasons: the applicant-facing edit forms, the reviewer-facing edit forms, or both.
|
|
173
|
+
* Leave a side out to inherit it from the layer below.
|
|
174
|
+
*/
|
|
175
|
+
export interface StatusReasonViewableBy {
|
|
176
|
+
applicant?: boolean;
|
|
177
|
+
reviewer?: boolean;
|
|
178
|
+
}
|
|
179
|
+
export type StatusReasonAudience = keyof StatusReasonViewableBy;
|
|
180
|
+
/**
|
|
181
|
+
* Per-status, per-audience switch for the framework's inline status-reason notifications: a boolean for
|
|
182
|
+
* every status and both audiences, or an object naming only the statuses to change, each as a boolean (both
|
|
183
|
+
* audiences) or a `StatusReasonViewableBy` (one side at a time).
|
|
184
|
+
*/
|
|
185
|
+
export type StatusReasonNotificationConfig = boolean | Partial<Record<RequirementStatus, boolean | StatusReasonViewableBy>>;
|
|
186
|
+
/**
|
|
187
|
+
* What the framework renders when neither `UIConfig` nor the prompt says otherwise: every status except
|
|
188
|
+
* PENDING, whose reasons are usually instructions ("Reviewer must assess...") rather than findings, for
|
|
189
|
+
* applicants and reviewers alike.
|
|
190
|
+
*/
|
|
191
|
+
export declare const DEFAULT_STATUS_REASON_NOTIFICATIONS: Record<RequirementStatus, Required<StatusReasonViewableBy>>;
|
|
154
192
|
/**
|
|
155
193
|
* Applicant prompts are wrapped by the default CL Form component, so any styling applied within the svelte prompt
|
|
156
194
|
* is independent (does not influence) of the layout of form elements that exist as part of the CL Form (eg. Validation notices).
|
|
@@ -167,6 +205,11 @@ export interface ApplicantPromptPageDefinition {
|
|
|
167
205
|
* CSS class settings specific to the corrections inline notification within the Form
|
|
168
206
|
*/
|
|
169
207
|
invalidatedInlineNotificationClass?: string;
|
|
208
|
+
/**
|
|
209
|
+
* CSS class applied to the wrapper around the requirement status reason notifications rendered below the
|
|
210
|
+
* prompt's form component.
|
|
211
|
+
*/
|
|
212
|
+
statusReasonInlineNotificationClass?: string;
|
|
170
213
|
}
|
|
171
214
|
export interface Terminologies {
|
|
172
215
|
/**
|
|
@@ -254,6 +297,16 @@ export interface UIConfig<PK extends string = PromptKey, RK extends string = Req
|
|
|
254
297
|
* individual prompt level if specific prompts desire specific layouts
|
|
255
298
|
*/
|
|
256
299
|
applicantPromptPage?: ApplicantPromptPageDefinition;
|
|
300
|
+
/**
|
|
301
|
+
* Global layer of `PromptDefinition.statusReasonNotifications`: which requirement status reasons the
|
|
302
|
+
* framework's edit forms render as inline notifications below a prompt's form component, and to which
|
|
303
|
+
* audience (`applicant` = applicant prompt page, accept page, opt-out modal; `reviewer` = the reviewer's
|
|
304
|
+
* inline and modal forms). A boolean sets every status for both audiences; an object such as
|
|
305
|
+
* `{ PENDING: { reviewer: true } }` changes only what it names on top of the built-in default (everything
|
|
306
|
+
* except PENDING). A prompt's own value then overrides this one, per status and per audience. Set false to
|
|
307
|
+
* render nothing anywhere and handle the `statusReasons` prop inside your own form components.
|
|
308
|
+
*/
|
|
309
|
+
statusReasonNotifications?: StatusReasonNotificationConfig;
|
|
257
310
|
/**
|
|
258
311
|
* Applicant Review submission page title and subtitle text.
|
|
259
312
|
*/
|
|
@@ -375,6 +428,8 @@ export declare class UIRegistry {
|
|
|
375
428
|
getWord(key: keyof Terminologies, count?: number, inclusive?: boolean): string;
|
|
376
429
|
getPlural(key: keyof Terminologies): string;
|
|
377
430
|
getPrompt(key: string): PromptDefinition | undefined;
|
|
431
|
+
protected statusReasonStatusCache: Map<string, Set<RequirementStatus>>;
|
|
432
|
+
statusReasonNotificationStatuses(key: string, audience: StatusReasonAudience): Set<RequirementStatus>;
|
|
378
433
|
getRequirement(key: string): RequirementDefinition | undefined;
|
|
379
434
|
getProgram(key: string): ProgramDefinition | undefined;
|
|
380
435
|
/**
|
package/dist/registry.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { plural } from 'txstate-utils';
|
|
2
2
|
const reviewDefaultSections = ['GENERAL', 'PROGRAM', 'REVIEWER', 'ACCEPTANCE'];
|
|
3
|
+
/**
|
|
4
|
+
* What the framework renders when neither `UIConfig` nor the prompt says otherwise: every status except
|
|
5
|
+
* PENDING, whose reasons are usually instructions ("Reviewer must assess...") rather than findings, for
|
|
6
|
+
* applicants and reviewers alike.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_STATUS_REASON_NOTIFICATIONS = {
|
|
9
|
+
DISQUALIFYING: { applicant: true, reviewer: false },
|
|
10
|
+
WARNING: { applicant: true, reviewer: false },
|
|
11
|
+
MET: { applicant: false, reviewer: false },
|
|
12
|
+
NOT_APPLICABLE: { applicant: false, reviewer: false },
|
|
13
|
+
PENDING: { applicant: false, reviewer: false }
|
|
14
|
+
};
|
|
3
15
|
/**
|
|
4
16
|
* Accepts either shape of a definition list and returns a key -> definition map. The keyed form
|
|
5
17
|
* carries its key in the object key, so it is grafted back on to keep every consumer's
|
|
@@ -92,6 +104,41 @@ export class UIRegistry {
|
|
|
92
104
|
getPrompt(key) {
|
|
93
105
|
return this.warnIfMissing('prompt', key, this.promptMap[key]);
|
|
94
106
|
}
|
|
107
|
+
statusReasonStatusCache = new Map();
|
|
108
|
+
// The statuses whose requirement reasons the framework renders below this prompt's edit form for the given
|
|
109
|
+
statusReasonNotificationStatuses(key, audience) {
|
|
110
|
+
const cacheKey = `${audience}:${key}`;
|
|
111
|
+
let statuses = this.statusReasonStatusCache.get(cacheKey);
|
|
112
|
+
if (statuses)
|
|
113
|
+
return statuses;
|
|
114
|
+
const allStatuses = Object.keys(DEFAULT_STATUS_REASON_NOTIFICATIONS);
|
|
115
|
+
const resolved = Object.fromEntries(allStatuses.map(status => [status, { ...DEFAULT_STATUS_REASON_NOTIFICATIONS[status] }]));
|
|
116
|
+
const setBoth = (status, on) => { resolved[status] = { applicant: on, reviewer: on }; };
|
|
117
|
+
for (const layer of [this.config.statusReasonNotifications, this.promptMap[key]?.statusReasonNotifications]) {
|
|
118
|
+
if (layer == null)
|
|
119
|
+
continue;
|
|
120
|
+
if (typeof layer === 'boolean') {
|
|
121
|
+
for (const status of allStatuses)
|
|
122
|
+
setBoth(status, layer);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
for (const [status, value] of Object.entries(layer)) {
|
|
126
|
+
if (value == null)
|
|
127
|
+
continue;
|
|
128
|
+
if (typeof value === 'boolean')
|
|
129
|
+
setBoth(status, value);
|
|
130
|
+
else {
|
|
131
|
+
if (value.applicant != null)
|
|
132
|
+
resolved[status].applicant = value.applicant;
|
|
133
|
+
if (value.reviewer != null)
|
|
134
|
+
resolved[status].reviewer = value.reviewer;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
statuses = new Set(allStatuses.filter(status => resolved[status][audience]));
|
|
139
|
+
this.statusReasonStatusCache.set(cacheKey, statuses);
|
|
140
|
+
return statuses;
|
|
141
|
+
}
|
|
95
142
|
getRequirement(key) {
|
|
96
143
|
return this.warnIfMissing('requirement', key, this.requirementMap[key]);
|
|
97
144
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Feedback } from '@txstate-mws/svelte-forms';
|
|
2
|
+
import type { StatusReason } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a prompt's `statusReasons` into the `Feedback` objects `FormInlineNotification` renders, so a
|
|
5
|
+
* requirement's reason looks exactly like a svelte-forms validation message.
|
|
6
|
+
*
|
|
7
|
+
* Entries with no reason text are dropped. Identical type+message pairs (the same requirement reached
|
|
8
|
+
* through several programs) collapse into one notification; when the prompt's entries span more than one
|
|
9
|
+
* program, each notification is titled with the program(s) it came from.
|
|
10
|
+
*
|
|
11
|
+
* `blamedOnly` keeps only entries whose requirement explicitly named this prompt in `blame`, and `statuses`
|
|
12
|
+
* keeps only entries with one of those statuses - the framework's edit forms pass both, with `statuses` from
|
|
13
|
+
* `uiRegistry.statusReasonNotificationStatuses(promptKey)`. A display component may do the same to follow
|
|
14
|
+
* the project's configuration, or pass neither to show everything it was handed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function statusReasonsToFeedback(statusReasons?: StatusReason[] | null, opts?: {
|
|
17
|
+
blamedOnly?: boolean;
|
|
18
|
+
statuses?: Iterable<string>;
|
|
19
|
+
}): Feedback[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirement status -> svelte-forms Feedback type. A status missing from this map is never surfaced as a
|
|
3
|
+
* notification, so this is the one place to narrow which statuses show.
|
|
4
|
+
*/
|
|
5
|
+
const statusFeedbackType = {
|
|
6
|
+
PENDING: 'info',
|
|
7
|
+
WARNING: 'warning',
|
|
8
|
+
DISQUALIFYING: 'error',
|
|
9
|
+
MET: 'success',
|
|
10
|
+
NOT_APPLICABLE: 'info'
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Turn a prompt's `statusReasons` into the `Feedback` objects `FormInlineNotification` renders, so a
|
|
14
|
+
* requirement's reason looks exactly like a svelte-forms validation message.
|
|
15
|
+
*
|
|
16
|
+
* Entries with no reason text are dropped. Identical type+message pairs (the same requirement reached
|
|
17
|
+
* through several programs) collapse into one notification; when the prompt's entries span more than one
|
|
18
|
+
* program, each notification is titled with the program(s) it came from.
|
|
19
|
+
*
|
|
20
|
+
* `blamedOnly` keeps only entries whose requirement explicitly named this prompt in `blame`, and `statuses`
|
|
21
|
+
* keeps only entries with one of those statuses - the framework's edit forms pass both, with `statuses` from
|
|
22
|
+
* `uiRegistry.statusReasonNotificationStatuses(promptKey)`. A display component may do the same to follow
|
|
23
|
+
* the project's configuration, or pass neither to show everything it was handed.
|
|
24
|
+
*/
|
|
25
|
+
export function statusReasonsToFeedback(statusReasons, opts) {
|
|
26
|
+
const allowed = opts?.statuses ? new Set(opts.statuses) : undefined;
|
|
27
|
+
const entries = (statusReasons ?? []).filter(r => (!opts?.blamedOnly || r.blamed) && (allowed == null || allowed.has(r.status)));
|
|
28
|
+
const programs = new Set(entries.map(r => r.programName));
|
|
29
|
+
const grouped = new Map();
|
|
30
|
+
for (const r of entries) {
|
|
31
|
+
const type = statusFeedbackType[r.status];
|
|
32
|
+
const message = r.statusReason?.trim();
|
|
33
|
+
if (!type || !message)
|
|
34
|
+
continue;
|
|
35
|
+
const key = `${type}\u0000${message}`;
|
|
36
|
+
const entry = grouped.get(key) ?? { type, message, programs: new Set() };
|
|
37
|
+
entry.programs.add(r.programName);
|
|
38
|
+
grouped.set(key, entry);
|
|
39
|
+
}
|
|
40
|
+
return [...grouped.values()].map(e => ({
|
|
41
|
+
type: e.type,
|
|
42
|
+
message: e.message,
|
|
43
|
+
extra: programs.size > 1 ? { title: [...e.programs].join(', ') } : undefined
|
|
44
|
+
}));
|
|
45
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -14,11 +14,13 @@ export interface AnsweredPrompt {
|
|
|
14
14
|
prestageData: Record<string, any>;
|
|
15
15
|
configurationData: Record<string, any>;
|
|
16
16
|
gatheredConfigData: Record<string, any>;
|
|
17
|
-
statusReasons:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
statusReasons: StatusReason[];
|
|
18
|
+
}
|
|
19
|
+
export interface StatusReason {
|
|
20
|
+
status: string;
|
|
21
|
+
statusReason: string | null;
|
|
22
|
+
programName: string;
|
|
23
|
+
blamed: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface PromptSection {
|
|
24
26
|
title: string;
|
|
@@ -70,8 +72,9 @@ export type OptOutApplication = ApplicationForDetails & {
|
|
|
70
72
|
};
|
|
71
73
|
export declare enum PromptIndicators {
|
|
72
74
|
AUTOMATION = 1,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
PENDING = 2,
|
|
76
|
+
WARNING = 3,
|
|
77
|
+
DISQUALIFYING = 4
|
|
75
78
|
}
|
|
76
79
|
export declare const translateMutations: {
|
|
77
80
|
submitAppRequest: string;
|
package/dist/types.js
CHANGED
|
@@ -2,8 +2,9 @@ export const phaseChangeMutations = ['submitAppRequest', 'returnToApplicant', 'c
|
|
|
2
2
|
export var PromptIndicators;
|
|
3
3
|
(function (PromptIndicators) {
|
|
4
4
|
PromptIndicators[PromptIndicators["AUTOMATION"] = 1] = "AUTOMATION";
|
|
5
|
-
PromptIndicators[PromptIndicators["
|
|
6
|
-
PromptIndicators[PromptIndicators["
|
|
5
|
+
PromptIndicators[PromptIndicators["PENDING"] = 2] = "PENDING";
|
|
6
|
+
PromptIndicators[PromptIndicators["WARNING"] = 3] = "WARNING";
|
|
7
|
+
PromptIndicators[PromptIndicators["DISQUALIFYING"] = 4] = "DISQUALIFYING";
|
|
7
8
|
})(PromptIndicators || (PromptIndicators = {}));
|
|
8
9
|
export const translateMutations = {
|
|
9
10
|
submitAppRequest: 'submitted request for review.',
|