@omnitend/dashboard-for-laravel 0.34.0 → 0.35.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.
@@ -44,12 +44,33 @@ interface Props {
44
44
  */
45
45
  cardTabs?: boolean;
46
46
  /**
47
- * Form-wide field layout: "vertical" (default, label above input) or
48
- * "horizontal" (label left, input right). Overridable per-field via
49
- * `field.layout`. A field with `span: true` always renders full-width,
50
- * regardless of layout.
47
+ * Form-wide field layout:
48
+ *
49
+ * - `"vertical"` (default) — label above input, always.
50
+ * - `"horizontal"` — label left, input right, always.
51
+ * - `"auto"` — horizontal when the form's **own container** is at least
52
+ * `layoutThreshold` px wide, vertical below that. Container-driven, not
53
+ * viewport-driven: a page narrowed by the dashboard sidebar, or a form
54
+ * inside a modal, stacks even though the window is wide (which no
55
+ * Bootstrap media query can see).
56
+ *
57
+ * Overridable per-field via `field.layout`. A field with `span: true`
58
+ * always renders full-width, regardless of layout.
51
59
  */
52
- layout?: "vertical" | "horizontal";
60
+ layout?: "vertical" | "horizontal" | "auto";
61
+ /**
62
+ * Container width (px) at or above which `layout: "auto"` goes horizontal.
63
+ * Ignored for the explicit `"vertical"`/`"horizontal"` layouts.
64
+ *
65
+ * Default 640, measured rather than guessed: with the default 3-column
66
+ * label the label's text area is `containerWidth / 4 - 18` px, so 640 gives
67
+ * it 142px — enough for a ~20-character label ("Unit price (ex VAT)"
68
+ * measures 128px at the theme's label font) to stay on one line, with the
69
+ * control column still 474px. Below ~584px that label starts wrapping,
70
+ * which is the cramped-label symptom this exists to avoid. Raise it if your
71
+ * labels run longer, or lower `labelCols` instead.
72
+ */
73
+ layoutThreshold?: number;
53
74
  /**
54
75
  * Label column width for horizontal layout (mirrors BFormGroup's
55
76
  * `labelCols`/`labelCols*` props). Overridable per-field via
@@ -73,13 +94,14 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
73
94
  onSubmit?: (() => any) | undefined;
74
95
  "onUpdate:activeTab"?: ((value: number) => any) | undefined;
75
96
  }>, {
76
- layout: "vertical" | "horizontal";
97
+ layout: "vertical" | "horizontal" | "auto";
77
98
  card: boolean;
78
99
  submitText: string;
79
100
  submitLoadingText: string;
80
101
  showSubmit: boolean;
81
102
  autoErrorTab: boolean;
82
103
  cardTabs: boolean;
104
+ layoutThreshold: number;
83
105
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
84
106
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
85
107
  export default _default;
@@ -67,6 +67,21 @@ export interface TableField {
67
67
  filter?: FilterType;
68
68
  filterOptions?: FilterOption[];
69
69
  filterPlaceholder?: string;
70
+ /**
71
+ * For a `select` filter on a **client-side** table: derive the dropdown's
72
+ * options from the distinct values present in the loaded rows. Defaults to
73
+ * `true` — with no `filterOptions` and no server `filterValues` the
74
+ * alternative is an empty dropdown, which can only ever be a bug.
75
+ *
76
+ * Set `false` to opt out (e.g. a column whose raw values are opaque ids you
77
+ * would rather label yourself via `filterOptions`).
78
+ *
79
+ * Explicit `filterOptions` and server-supplied `filterValues` still WIN, in
80
+ * that order. Provider/API and Inertia modes ignore this flag: they only
81
+ * ever hold one page of rows, so there is no complete value set to derive
82
+ * from (see `getFieldFilterOptions`).
83
+ */
84
+ deriveFilterOptions?: boolean;
70
85
  /**
71
86
  * Fixed width for this column, applied via a `<colgroup>` `<col>`. A number
72
87
  * is treated as pixels (`200` → `200px`); a string is used verbatim
@@ -233,6 +248,19 @@ export interface Props<TItem = any> {
233
248
  * borderless variant — the same header, filter row, table and pagination
234
249
  * rendered directly on the page background (no card border/shadow), for
235
250
  * data-heavy admin index pages that read as busy inside a card.
251
+ *
252
+ * The table renders FLUSH to the card border (rows and stripes reach the
253
+ * edge) while the header and pagination stay padded, which requires the card
254
+ * to clip to its radius (`overflow: hidden`).
255
+ *
256
+ * **Consequence for slot content:** anything you render into a slot that
257
+ * positions itself OUTSIDE the card and does not teleport is clipped at the
258
+ * card edge. Library components are unaffected — `DDropdown` teleports to
259
+ * `<body>` by default, and the column-filter menus position themselves
260
+ * `fixed` — so this only bites a hand-rolled `position: absolute` overlay
261
+ * (a custom popover in a `cell(<key>)` or `#header` slot). Teleport it, or
262
+ * use `:card="false"`. Narrowing the clip to the table region is tracked
263
+ * as #166.
236
264
  */
237
265
  card?: boolean;
238
266
  /**
@@ -253,8 +281,13 @@ export interface Props<TItem = any> {
253
281
  * **"horizontal"** to match the Omni Tend form convention (a raw modal form
254
282
  * previously fell back to DXForm's vertical default, which looked out of
255
283
  * place next to horizontal page forms). Pass `"vertical"` to opt out.
284
+ *
285
+ * `"auto"` hands the decision to `DXForm`'s container-width observer, which
286
+ * measures the FORM rather than the window — so a modal narrowed by its own
287
+ * breakpoint stacks to vertical even on a wide viewport, where a media query
288
+ * would still report "plenty of room". See `useContainerWidth`.
256
289
  */
257
- editLayout?: 'vertical' | 'horizontal';
290
+ editLayout?: 'vertical' | 'horizontal' | 'auto';
258
291
  /**
259
292
  * Label column width for the edit modal's horizontal layout, forwarded to
260
293
  * `DXForm`'s `labelCols` (a single width or a per-breakpoint object). Only
@@ -390,6 +423,11 @@ declare const _default: <T = any>(__VLS_props: NonNullable<Awaited<typeof __VLS_
390
423
  }>): void;
391
424
  attrs: any;
392
425
  slots: {
426
+ [x: `head-end(${string})`]: ((props: {
427
+ field: TableField;
428
+ label: string;
429
+ }) => any) | undefined;
430
+ } & {
393
431
  [x: string]: ((props: any) => any) | undefined;
394
432
  } & {
395
433
  [x: string]: ((props: any) => any) | undefined;
@@ -27,8 +27,13 @@ interface Props {
27
27
  pendingAction: 'save' | 'delete' | null;
28
28
  /** Delete endpoint — gates the Delete button. */
29
29
  deleteUrl?: string;
30
- /** Field layout forwarded to the modal's DXForm (defaults handled upstream). */
31
- layout?: 'vertical' | 'horizontal';
30
+ /**
31
+ * Field layout forwarded to the modal's DXForm (defaults handled upstream).
32
+ * Includes `"auto"`, which lets the form stack to vertical when the MODAL is
33
+ * narrow even though the viewport is wide — the container-query case a
34
+ * viewport media query cannot see.
35
+ */
36
+ layout?: 'vertical' | 'horizontal' | 'auto';
32
37
  /** Label column width forwarded to DXForm (horizontal layout). */
33
38
  labelCols?: LabelCols;
34
39
  /** Wrap the modal form in a card (opt-in — off by default). */
@@ -0,0 +1,96 @@
1
+ /**
2
+ * useContainerWidth — container-query-style responsiveness for Vue components.
3
+ *
4
+ * Bootstrap's breakpoints are **media** queries: they only ever see the WINDOW.
5
+ * A dashboard page is narrowed by the persistent sidebar (and a form inside a
6
+ * modal is narrower still), so a component can have very little room while the
7
+ * viewport is wide — and no media query ever fires. This composable closes that
8
+ * gap by observing an element's **own rendered width** with a `ResizeObserver`
9
+ * and exposing it reactively, so the template can pick a layout from the space
10
+ * it actually has.
11
+ *
12
+ * ```ts
13
+ * const { containerRef, width, isBelow, isNarrowerThan } = useContainerWidth({
14
+ * threshold: 640,
15
+ * });
16
+ * // <div ref="containerRef"> … </div>
17
+ * ```
18
+ *
19
+ * One observer can drive several independent decisions — `isBelow` uses the
20
+ * configured `threshold`, and `isNarrowerThan(n)` answers for any other width:
21
+ *
22
+ * ```ts
23
+ * const stacked = computed(() => isNarrowerThan(576));
24
+ * const hideDetailColumn = computed(() => isNarrowerThan(900));
25
+ * ```
26
+ */
27
+ import { type Ref } from "vue";
28
+ /**
29
+ * What to observe: a template ref, or a getter (so the element can be resolved
30
+ * lazily / conditionally — e.g. `() => enabled ? el.value : null` to observe
31
+ * nothing at all).
32
+ */
33
+ export type ContainerWidthTarget = Ref<HTMLElement | null | undefined> | (() => HTMLElement | null | undefined);
34
+ export interface UseContainerWidthOptions {
35
+ /**
36
+ * Width (px) that `isBelow` compares against. Omit if you only need
37
+ * `width` / `isNarrowerThan`. Pass a getter (`() => props.threshold`) when
38
+ * it can change at runtime — `isBelow` re-evaluates when it does.
39
+ */
40
+ threshold?: number | (() => number | undefined);
41
+ /**
42
+ * Hysteresis band (px) applied to `isBelow` only, to stop a layout flipping
43
+ * back and forth at the boundary. Once `isBelow` is true it stays true
44
+ * until the width reaches `threshold + hysteresis`.
45
+ *
46
+ * Defaults to `0` so `isBelow` means exactly `width < threshold` unless you
47
+ * ask for otherwise. Opt in whenever the layout you drive from it can
48
+ * change the observed width — the classic loop is: narrow layout is taller
49
+ * → an ancestor with `overflow:auto` gains a vertical scrollbar → the
50
+ * container loses ~15-17px → it crosses back over the threshold → repeat.
51
+ * A band wider than a scrollbar (24px is comfortable) breaks that cycle.
52
+ * (`isNarrowerThan` is deliberately raw — it holds no per-width state.)
53
+ */
54
+ hysteresis?: number;
55
+ /**
56
+ * Width assumed before the first measurement, and permanently where there
57
+ * is no `ResizeObserver` (SSR, or a very old browser).
58
+ *
59
+ * Defaults to `0` — i.e. "assume the narrowest until proven otherwise", so
60
+ * an unmeasured render picks the STACKED layout. That is the mobile-first
61
+ * choice and the safe one: a stacked layout is legible at every width,
62
+ * whereas a wide layout guessed wrongly truncates content. It also means
63
+ * the server-rendered markup matches the first client render.
64
+ */
65
+ initialWidth?: number;
66
+ /**
67
+ * Which box to report. `content-box` (default) is the space actually
68
+ * available to children — what a layout decision usually wants.
69
+ * `border-box` matches `getBoundingClientRect().width`.
70
+ */
71
+ box?: "content-box" | "border-box";
72
+ /**
73
+ * Extra debounce (ms) before publishing a new width. Defaults to `0`:
74
+ * `ResizeObserver` already batches to one delivery per frame, and the
75
+ * callback reads the size the observer *hands* it (no forced layout), so
76
+ * there is nothing to thrash. Set it only for genuinely expensive
77
+ * downstream work — a timer adds a frame of visible lag.
78
+ */
79
+ debounce?: number;
80
+ }
81
+ export interface UseContainerWidthReturn {
82
+ /** Bind to the element you want measured (`<div ref="containerRef">`). */
83
+ containerRef: Ref<HTMLElement | null>;
84
+ /** The observed width in px. `initialWidth` until first measured. */
85
+ width: Readonly<Ref<number>>;
86
+ /** False until a real measurement has landed (always false without a `ResizeObserver`). */
87
+ hasMeasured: Readonly<Ref<boolean>>;
88
+ /** `width < threshold`, with `hysteresis` applied. False when no `threshold` was given. */
89
+ isBelow: Readonly<Ref<boolean>>;
90
+ /** Raw `width < px`, for driving other decisions off the same observer. */
91
+ isNarrowerThan: (px: number) => boolean;
92
+ /** Disconnect early. Called automatically when the owning scope is disposed. */
93
+ stop: () => void;
94
+ }
95
+ export declare function useContainerWidth(options?: UseContainerWidthOptions): UseContainerWidthReturn;
96
+ export declare function useContainerWidth(target: ContainerWidthTarget, options?: UseContainerWidthOptions): UseContainerWidthReturn;