foldkit 0.117.0 → 0.118.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/dom/dom.d.ts CHANGED
@@ -132,16 +132,26 @@ export declare const scrollIntoViewAfterPaint: (selector: string, options?: Read
132
132
  * Defaults to `{ block: 'center' }`; pass a different `block` when the
133
133
  * target should land at a specific position when it does need to scroll.
134
134
  *
135
+ * `when` selects the timing gate. `'Paint'` (the default) waits for
136
+ * `Render.afterPaint`, so the scroll lands after the target is on screen.
137
+ * `'Commit'` waits for `Render.afterCommit` instead, so the scroll lands in
138
+ * the same frame the DOM patch applies, before the browser paints. Use
139
+ * `'Commit'` when the target is brought into view and scrolled by the same
140
+ * Message (such as a menu opening), so it appears already scrolled rather
141
+ * than visibly jumping.
142
+ *
135
143
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
136
144
  *
137
145
  * @example
138
146
  * ```typescript
139
147
  * Dom.scrollIntoViewIfNotVisible('#active-nav-link')
140
148
  * Dom.scrollIntoViewIfNotVisible('#active-nav-link', { block: 'nearest' })
149
+ * Dom.scrollIntoViewIfNotVisible('#active-nav-link', { when: 'Commit' })
141
150
  * ```
142
151
  */
143
152
  export declare const scrollIntoViewIfNotVisible: (selector: string, options?: Readonly<{
144
153
  block?: ScrollLogicalPosition;
154
+ when?: "Paint" | "Commit";
145
155
  }>) => Effect.Effect<void, ElementNotFound>;
146
156
  /** Direction for focus advancement: forward or backward in tab order. */
147
157
  export type FocusDirection = 'Next' | 'Previous';
@@ -1 +1 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAiC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KACvE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAsElC,CAAA;AAuBJ;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAyBlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAcJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAkBlC,CAAA;AAEJ,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAiC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KACvE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAsElC,CAAA;AAuBJ;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAyBlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAcJ;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IACjB,KAAK,CAAC,EAAE,qBAAqB,CAAA;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC1B,CAAC,KACD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAuBlC,CAAA;AAEJ,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
package/dist/dom/dom.js CHANGED
@@ -250,16 +250,26 @@ const findScrollParent = (element) => {
250
250
  * Defaults to `{ block: 'center' }`; pass a different `block` when the
251
251
  * target should land at a specific position when it does need to scroll.
252
252
  *
253
+ * `when` selects the timing gate. `'Paint'` (the default) waits for
254
+ * `Render.afterPaint`, so the scroll lands after the target is on screen.
255
+ * `'Commit'` waits for `Render.afterCommit` instead, so the scroll lands in
256
+ * the same frame the DOM patch applies, before the browser paints. Use
257
+ * `'Commit'` when the target is brought into view and scrolled by the same
258
+ * Message (such as a menu opening), so it appears already scrolled rather
259
+ * than visibly jumping.
260
+ *
253
261
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
254
262
  *
255
263
  * @example
256
264
  * ```typescript
257
265
  * Dom.scrollIntoViewIfNotVisible('#active-nav-link')
258
266
  * Dom.scrollIntoViewIfNotVisible('#active-nav-link', { block: 'nearest' })
267
+ * Dom.scrollIntoViewIfNotVisible('#active-nav-link', { when: 'Commit' })
259
268
  * ```
260
269
  */
261
270
  export const scrollIntoViewIfNotVisible = (selector, options) => Effect.gen(function* () {
262
- yield* afterPaint;
271
+ const when = options?.when ?? 'Paint';
272
+ yield* M.value(when).pipe(M.when('Paint', () => afterPaint), M.when('Commit', () => afterCommit), M.exhaustive);
263
273
  const element = yield* queryHTMLElement(selector);
264
274
  Option.match(findScrollParent(element), {
265
275
  onNone: () => element.scrollIntoView({ block: options?.block ?? 'center' }),
@@ -102,6 +102,31 @@ export declare const string: <K extends string>(name: K) => Biparser<Record<K, s
102
102
  * ```
103
103
  */
104
104
  export declare const int: <K extends string>(name: K) => Biparser<Record<K, number>>;
105
+ export declare const __isSingleSegment: (segment: string) => boolean;
106
+ /**
107
+ * Creates a parser that captures a URL segment and decodes it through an
108
+ * Effect `Schema`, producing a named field of the schema's decoded type.
109
+ *
110
+ * Decodes the raw segment when parsing and encodes the value back when
111
+ * printing, so branded ids, refined strings, and string-literal unions
112
+ * round-trip through the route. The decoded type flows straight into the
113
+ * route value, so a model carries `UserId` rather than a bare `string`.
114
+ *
115
+ * The schema's encoded form must be a single URL segment string. For shapes
116
+ * that span multiple segments or live in the query string, use `rest` or
117
+ * `query`.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const UserId = S.String.pipe(S.brand('UserId'))
122
+ * pipe(literal('users'), slash(schemaSegment('userId', UserId)), mapTo(UserRoute))
123
+ * // parses /users/abc into { _tag: 'UserRoute', userId: UserId.make('abc') }
124
+ * ```
125
+ *
126
+ * @param name - The field name the decoded value is captured under.
127
+ * @param schema - A codec whose encoded form is a single URL segment string.
128
+ */
129
+ export declare const schemaSegment: <K extends string, A, I extends string>(name: K, schema: Schema.Codec<A, I>) => Biparser<Record<K, A>>;
105
130
  /**
106
131
  * A parser that matches the root path with no remaining segments.
107
132
  *
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/route/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/route/parser.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ export class ParseError extends Data.TaggedError('ParseError') {
10
10
  const makeTerminalParser = (parser) =>
11
11
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
12
12
  parser;
13
+ const pathToSegments = flow(String.split('/'), Array.filter(String.isNonEmpty));
13
14
  /**
14
15
  * Creates a parser that matches an exact URL path segment.
15
16
  *
@@ -94,6 +95,66 @@ export const int = (name) => param(`integer (${name})`, segment => {
94
95
  : /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
95
96
  Effect.succeed({ [name]: parsed });
96
97
  }, record => record[name].toString());
98
+ export const __isSingleSegment = (segment) => Array.matchLeft(pathToSegments(segment), {
99
+ onEmpty: () => false,
100
+ onNonEmpty: (head, tail) => head === segment && Array.isArrayEmpty(tail),
101
+ });
102
+ const warnIfNotSingleSegment = (name, segment) => Effect.sync(() => {
103
+ if (import.meta.hot && !__isSingleSegment(segment)) {
104
+ console.warn(`[foldkit] schemaSegment('${name}') encoded to '${segment}', which is not ` +
105
+ 'a single URL segment. Parsing re-splits the path on slashes and drops ' +
106
+ 'empty pieces, so this value will not round-trip. Encode to one ' +
107
+ 'non-empty, slash-free segment, or use rest for multi-segment values.');
108
+ }
109
+ });
110
+ /**
111
+ * Creates a parser that captures a URL segment and decodes it through an
112
+ * Effect `Schema`, producing a named field of the schema's decoded type.
113
+ *
114
+ * Decodes the raw segment when parsing and encodes the value back when
115
+ * printing, so branded ids, refined strings, and string-literal unions
116
+ * round-trip through the route. The decoded type flows straight into the
117
+ * route value, so a model carries `UserId` rather than a bare `string`.
118
+ *
119
+ * The schema's encoded form must be a single URL segment string. For shapes
120
+ * that span multiple segments or live in the query string, use `rest` or
121
+ * `query`.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const UserId = S.String.pipe(S.brand('UserId'))
126
+ * pipe(literal('users'), slash(schemaSegment('userId', UserId)), mapTo(UserRoute))
127
+ * // parses /users/abc into { _tag: 'UserRoute', userId: UserId.make('abc') }
128
+ * ```
129
+ *
130
+ * @param name - The field name the decoded value is captured under.
131
+ * @param schema - A codec whose encoded form is a single URL segment string.
132
+ */
133
+ export const schemaSegment = (name, schema) => {
134
+ const decode = Schema.decodeUnknownEffect(schema);
135
+ const encode = Schema.encodeEffect(schema);
136
+ return {
137
+ parse: segments => Array.matchLeft(segments, {
138
+ onEmpty: () => Effect.fail(new ParseError({
139
+ message: `Expected ${name}`,
140
+ expected: name,
141
+ actual: 'end of path',
142
+ position: 0,
143
+ })),
144
+ onNonEmpty: (head, tail) => pipe(head, decode, Effect.mapError(error => new ParseError({
145
+ message: `Invalid ${name}: ${error.message}`,
146
+ expected: name,
147
+ actual: head,
148
+ })), Effect.map(value => [Record.singleton(name, value), tail])),
149
+ }),
150
+ print: (value, state) => pipe(value[name], encode, Effect.mapError(error => new ParseError({
151
+ message: `Failed to encode ${name}: ${error.message}`,
152
+ })), Effect.tap(segment => warnIfNotSingleSegment(name, segment)), Effect.map(segment => ({
153
+ ...state,
154
+ segments: [...state.segments, segment],
155
+ }))),
156
+ };
157
+ };
97
158
  /**
98
159
  * A parser that matches the root path with no remaining segments.
99
160
  *
@@ -275,7 +336,6 @@ export const query = (schema) => (parser) => {
275
336
  };
276
337
  return makeTerminalParser(queryParser);
277
338
  };
278
- const pathToSegments = flow(String.split('/'), Array.filter(String.isNonEmpty));
279
339
  const parseUrl = (parser) => (url) => pipe(pathToSegments(url.pathname), segments => parser.parse(segments, Option.getOrUndefined(url.search)), Effect.flatMap(complete), Effect.map(([value]) => value));
280
340
  /**
281
341
  * Parses a URL against a parser, falling back to a not-found route if no
@@ -1,3 +1,3 @@
1
- export { ParseError, literal, param, string, int, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
1
+ export { ParseError, literal, param, string, int, schemaSegment, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
2
2
  export type { ParseResult, Biparser, Router, TerminalParser, ExtendableBiparser, Parser, } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/route/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,OAAO,EACP,KAAK,EACL,MAAM,EACN,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,KAAK,EACL,oBAAoB,EACpB,CAAC,GACF,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,MAAM,GACP,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/route/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,OAAO,EACP,KAAK,EACL,MAAM,EACN,GAAG,EACH,aAAa,EACb,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,KAAK,EACL,oBAAoB,EACpB,CAAC,GACF,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,MAAM,GACP,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { ParseError, literal, param, string, int, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
1
+ export { ParseError, literal, param, string, int, schemaSegment, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.117.0",
3
+ "version": "0.118.0",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",