@proteos/sdk 0.55.0 → 0.57.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.
@@ -24,6 +24,7 @@ export const LayoutElementType = {
24
24
  List: 'list',
25
25
  WorkflowTrigger: 'workflow_trigger',
26
26
  SchedulingPicker: 'scheduling_picker',
27
+ Calendar: 'calendar',
27
28
  } as const
28
29
  export type LayoutElementType = (typeof LayoutElementType)[keyof typeof LayoutElementType]
29
30
 
@@ -276,6 +277,50 @@ export type SchedulingPickerElement = CommonProps & {
276
277
  is_header_hidden?: boolean
277
278
  }
278
279
 
280
+ /** The grid views a `calendar` element can show; mirrors `metamodel.CalendarViewKinds`. */
281
+ export const CALENDAR_VIEW_KINDS = ['day', 'week', 'month', 'agenda'] as const
282
+ export type CalendarViewKind = (typeof CALENDAR_VIEW_KINDS)[number]
283
+
284
+ /**
285
+ * Cap on `overlay_user_ids` (one team colour per overlaid user); mirrors
286
+ * `metamodel.CalendarMaxOverlayUsers`.
287
+ */
288
+ export const CALENDAR_MAX_OVERLAY_USERS = 8
289
+
290
+ /**
291
+ * A user's calendar embedded in a page: the in-page counterpart of
292
+ * `/org/:orgId/calendar`. `user_id` is `$current_user`, a Liquid template over
293
+ * the page scope (`{{ record.owner.id }}`) or a literal user id; writes (new
294
+ * event, drag, edit) are offered only when it resolves to the viewer — unless
295
+ * `is_hosted_create_allowed` opens creation through the displayed user.
296
+ */
297
+ export type CalendarElement = CommonProps & {
298
+ type: 'calendar'
299
+ user_id: string
300
+ /** Initial view; `day` when absent. */
301
+ default_view?: CalendarViewKind
302
+ /** Views the switcher offers; only `default_view` when absent. Must contain it. */
303
+ views?: CalendarViewKind[]
304
+ /** Liquid template for the initial anchor day (day key or datetime); today when absent. */
305
+ date?: string
306
+ /**
307
+ * Record pages only: show only events linked to the page record
308
+ * (`record_entity_slug` + `record_id`) instead of the user's whole calendar.
309
+ */
310
+ is_record_scoped?: boolean
311
+ /**
312
+ * Up to `CALENDAR_MAX_OVERLAY_USERS` further users drawn on the same grid in
313
+ * team colours, read-only. Each resolves like `user_id`.
314
+ */
315
+ overlay_user_ids?: string[]
316
+ /**
317
+ * When the calendar shows someone else: "New event" and drag-select create
318
+ * THROUGH that user (the viewer hosts the request, the event lands on the
319
+ * displayed user's calendar). Edit and move stay owner-only.
320
+ */
321
+ is_hosted_create_allowed?: boolean
322
+ }
323
+
279
324
  export type TextVariant = 'heading' | 'subheading' | 'body' | 'caption' | 'callout' | 'block-title'
280
325
 
281
326
  export type TextElement = CommonProps & {
@@ -300,6 +345,7 @@ export type LayoutElement =
300
345
  | ListElement
301
346
  | WorkflowTriggerElement
302
347
  | SchedulingPickerElement
348
+ | CalendarElement
303
349
 
304
350
  // ──────────────────────────────────────────────────────────── Schemas ──
305
351
 
@@ -439,5 +485,16 @@ export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
439
485
  contact_id: z.string().optional(),
440
486
  is_header_hidden: z.boolean().optional(),
441
487
  }),
488
+ z.object({
489
+ type: z.literal('calendar'),
490
+ ...commonPropsShape,
491
+ user_id: z.string().min(1),
492
+ default_view: z.enum(CALENDAR_VIEW_KINDS).optional(),
493
+ views: z.array(z.enum(CALENDAR_VIEW_KINDS)).min(1).optional(),
494
+ date: z.string().optional(),
495
+ is_record_scoped: z.boolean().optional(),
496
+ overlay_user_ids: z.array(z.string().min(1)).max(CALENDAR_MAX_OVERLAY_USERS).optional(),
497
+ is_hosted_create_allowed: z.boolean().optional(),
498
+ }),
442
499
  ]),
443
500
  ) as unknown as z.ZodType<LayoutElement>
@@ -17,6 +17,10 @@ export {
17
17
  lookupPrimaryControl,
18
18
  } from './control-registry.js'
19
19
  export {
20
+ CALENDAR_MAX_OVERLAY_USERS,
21
+ CALENDAR_VIEW_KINDS,
22
+ type CalendarElement,
23
+ type CalendarViewKind,
20
24
  type CardElement,
21
25
  type ColumnElement,
22
26
  type ComponentElement,
@@ -29,17 +33,19 @@ export {
29
33
  LayoutTabSchema,
30
34
  type ListElement,
31
35
  type RecordFilterElement,
32
- type SchedulingPickerElement,
33
- type WorkflowTriggerElement,
34
36
  type RelatedListElement,
35
37
  type RelatedRecordElement,
36
38
  type RowElement,
39
+ type SchedulingPickerElement,
37
40
  type SectionElement,
38
41
  type TabsElement,
39
42
  type TextElement,
40
43
  type TextVariant,
44
+ type WorkflowTriggerElement,
41
45
  } from './elements.js'
42
46
  export {
47
+ isPageBackground,
48
+ PAGE_BACKGROUND_COLOR_RE,
43
49
  PAGE_BACKGROUND_TOKENS,
44
50
  type PageBackgroundToken,
45
51
  type PageLayout,
@@ -49,4 +55,13 @@ export {
49
55
  type PageStyle,
50
56
  PageStyleSchema,
51
57
  } from './page-layout.js'
52
- export { type SizeValue, SizeValueSchema } from './size-value.js'
58
+ export {
59
+ isLengthValue,
60
+ isStyleSizeValue,
61
+ type LengthValue,
62
+ LengthValueSchema,
63
+ type SizeValue,
64
+ SizeValueSchema,
65
+ type StyleSizeValue,
66
+ StyleSizeValueSchema,
67
+ } from './size-value.js'
@@ -1,6 +1,11 @@
1
1
  import { z } from 'zod'
2
2
  import { type LayoutElement, LayoutElementSchema } from './elements.js'
3
- import { type SizeValue, SizeValueSchema } from './size-value.js'
3
+ import {
4
+ type SizeValue,
5
+ SizeValueSchema,
6
+ type StyleSizeValue,
7
+ StyleSizeValueSchema,
8
+ } from './size-value.js'
4
9
 
5
10
  /**
6
11
  * Side panel slot. Rendered as a sticky-by-default right rail; collapses
@@ -44,6 +49,26 @@ export const PAGE_BACKGROUND_TOKENS = [
44
49
 
45
50
  export type PageBackgroundToken = (typeof PAGE_BACKGROUND_TOKENS)[number]
46
51
 
52
+ /**
53
+ * A raw CSS color a page background may use instead of a token: a hex literal
54
+ * or one of a few color functions with a restricted argument charset (no `;{}`,
55
+ * no `url(`), so a background can never smuggle arbitrary CSS. Keep in sync
56
+ * with `pageBackgroundColorRe` in the metadata-service layout validator.
57
+ */
58
+ export const PAGE_BACKGROUND_COLOR_RE =
59
+ /^(#[0-9a-fA-F]{3,8}|(rgb|rgba|hsl|hsla|oklch|oklab)\([0-9a-zA-Z.,%/ +-]+\))$/
60
+
61
+ /** True when `text` is a background the server accepts: a token or a color
62
+ * matching PAGE_BACKGROUND_COLOR_RE, at most 64 characters. */
63
+ export function isPageBackground(text: string): boolean {
64
+ const value = text.trim()
65
+ if (value.length === 0 || value.length > 64) return false
66
+ return (
67
+ (PAGE_BACKGROUND_TOKENS as readonly string[]).includes(value) ||
68
+ PAGE_BACKGROUND_COLOR_RE.test(value)
69
+ )
70
+ }
71
+
47
72
  /**
48
73
  * Per-page presentation of a standalone page shell: background color plus the
49
74
  * content container's max-width and side / top padding. Every field is
@@ -56,20 +81,24 @@ export type PageBackgroundToken = (typeof PAGE_BACKGROUND_TOKENS)[number]
56
81
  * area rather than a standalone shell.
57
82
  */
58
83
  export interface PageStyle {
59
- /** Design-token key (see PAGE_BACKGROUND_TOKENS) or a raw CSS color. */
84
+ /** Design-token key (see PAGE_BACKGROUND_TOKENS) or a raw CSS color
85
+ * matching PAGE_BACKGROUND_COLOR_RE. */
60
86
  background?: string
61
- /** Content max-width; `"fill"` / `"auto"` removes the cap. */
62
- max_width?: SizeValue
87
+ /** Content max-width; `"fill"` / `"auto"` removes the cap. Never a fraction. */
88
+ max_width?: StyleSizeValue
63
89
  /** Horizontal / vertical padding of the content container (Tailwind px/py). */
64
- padding_x?: SizeValue
65
- padding_y?: SizeValue
90
+ padding_x?: StyleSizeValue
91
+ padding_y?: StyleSizeValue
66
92
  }
67
93
 
68
94
  export const PageStyleSchema = z.object({
69
- background: z.string().optional(),
70
- max_width: SizeValueSchema.optional(),
71
- padding_x: SizeValueSchema.optional(),
72
- padding_y: SizeValueSchema.optional(),
95
+ background: z
96
+ .string()
97
+ .refine(isPageBackground, 'must be a design token or a hex / rgb / hsl / oklch / oklab color')
98
+ .optional(),
99
+ max_width: StyleSizeValueSchema.optional(),
100
+ padding_x: StyleSizeValueSchema.optional(),
101
+ padding_y: StyleSizeValueSchema.optional(),
73
102
  })
74
103
 
75
104
  /**
@@ -21,6 +21,43 @@ export type SizeValue =
21
21
 
22
22
  const SIZE_VALUE_STRING_RE = /^(\d+\/\d+|\d+px|\d+%|auto|fill)$/
23
23
 
24
+ /**
25
+ * A fixed CSS length — the subset of SizeValue the server accepts for
26
+ * `min_height` ("Npx" / "N%"). Mirrors `minHeightStringRe` in the
27
+ * metadata-service layout validator.
28
+ */
29
+ export type LengthValue = `${number}px` | `${number}%`
30
+
31
+ const LENGTH_VALUE_RE = /^(\d+px|\d+%)$/
32
+
33
+ export function isLengthValue(text: string): text is LengthValue {
34
+ return LENGTH_VALUE_RE.test(text)
35
+ }
36
+
37
+ export const LengthValueSchema: z.ZodType<LengthValue> = z
38
+ .string()
39
+ .regex(LENGTH_VALUE_RE, 'must match Npx or N%') as unknown as z.ZodType<LengthValue>
40
+
41
+ /**
42
+ * A page-style dimension (`layout.style.max_width` / `padding_*`): a length,
43
+ * "auto" or "fill" — never a fraction. Mirrors `styleSizeValueStringRe` in the
44
+ * metadata-service layout validator.
45
+ */
46
+ export type StyleSizeValue = LengthValue | 'auto' | 'fill'
47
+
48
+ const STYLE_SIZE_VALUE_RE = /^(\d+px|\d+%|auto|fill)$/
49
+
50
+ export function isStyleSizeValue(text: string): text is StyleSizeValue {
51
+ return STYLE_SIZE_VALUE_RE.test(text)
52
+ }
53
+
54
+ export const StyleSizeValueSchema: z.ZodType<StyleSizeValue> = z
55
+ .string()
56
+ .regex(
57
+ STYLE_SIZE_VALUE_RE,
58
+ 'must match Npx, N%, "auto", or "fill"',
59
+ ) as unknown as z.ZodType<StyleSizeValue>
60
+
24
61
  export const SizeValueSchema: z.ZodType<SizeValue> = z.union([
25
62
  z.number().min(0).max(1).describe('Fraction in [0, 1]'),
26
63
  z.string().regex(SIZE_VALUE_STRING_RE, 'must match n/m, Npx, N%, "auto", or "fill"'),
package/src/meta/types.ts CHANGED
@@ -2046,6 +2046,22 @@ export interface DesignReferenceContent {
2046
2046
  * hook. An object rather than a bare string because a bare string IS a valid
2047
2047
  * user value (clients send bare ids). Mirrors `metamodel.CurrentUserDefault`.
2048
2048
  */
2049
+ /**
2050
+ * The signed-in user as a VALUE: a `FilterElement.value` (any field —
2051
+ * data-service and the page renderer substitute the caller's id), a component
2052
+ * prop, a `calendar` element's `user_id` / `overlay_user_ids`, a string
2053
+ * literal in the records SQL dialect. The `$` prefix keeps it apart from every
2054
+ * id, slug or text, so no slot has to know the attribute type. The Liquid
2055
+ * scope exposes the same person as the object `{{ current_user.id }}`.
2056
+ * Mirrors `common.CurrentUser`.
2057
+ */
2058
+ export const CURRENT_USER = '$current_user'
2059
+
2060
+ /** True when a string slot carries the current-user sentinel. */
2061
+ export function isCurrentUser(value: unknown): value is typeof CURRENT_USER {
2062
+ return value === CURRENT_USER
2063
+ }
2064
+
2049
2065
  export const CURRENT_USER_DEFAULT = { type: 'current_user' } as const
2050
2066
  export type CurrentUserDefault = { type: 'current_user' }
2051
2067